Bug 1347605 - Ensure standalone pwa's are singletons

This commit is contained in:
Dale Harvey 2017-03-29 18:44:30 +01:00
Родитель bfd998a06c
Коммит 7b024ea9c8
9 изменённых файлов: 257 добавлений и 13 удалений

Просмотреть файл

@ -317,6 +317,12 @@
<activity android:name="org.mozilla.gecko.webapps.WebAppActivity"
android:theme="@style/Theme.AppCompat.NoActionBar" />
<!-- Declare a predefined number of WebApp<num> activities. These are
used so that each web app can launch in its own activity. -->
#define FRAGMENT WebAppManifestFragment.xml.frag.in
#include WebAppFragmentRepeater.inc
<!-- Service to handle requests from overlays. -->
<service android:name="org.mozilla.gecko.overlays.service.OverlayActionService" />

Просмотреть файл

@ -0,0 +1,32 @@
#define APPNUM 0
#include @FRAGMENT@
#define APPNUM 1
#include @FRAGMENT@
#define APPNUM 2
#include @FRAGMENT@
#define APPNUM 3
#include @FRAGMENT@
#define APPNUM 4
#include @FRAGMENT@
#define APPNUM 5
#include @FRAGMENT@
#define APPNUM 6
#include @FRAGMENT@
#define APPNUM 7
#include @FRAGMENT@
#define APPNUM 8
#include @FRAGMENT@
#define APPNUM 9
#include @FRAGMENT@
#undef APPNUM
#undef FRAGMENT

Просмотреть файл

@ -0,0 +1,9 @@
<activity android:name="org.mozilla.gecko.webapps.WebApps$WebApp@APPNUM@"
android:label="WebApp"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|locale|layoutDirection|smallestScreenSize|screenLayout"
android:windowSoftInputMode="stateUnspecified|adjustResize"
android:theme="@style/Gecko.App"
android:taskAffinity="org.mozilla.gecko.webapps.WebApps@APPNUM@"
android:launchMode="singleTask"
android:exported="true"
/>

Просмотреть файл

@ -2242,9 +2242,7 @@ public abstract class GeckoApp
GeckoAppShell.setGeckoInterface(this);
GeckoAppShell.setScreenOrientationDelegate(this);
if (lastSelectedTabId >= 0 && (lastActiveGeckoApp == null || lastActiveGeckoApp.get() != this)) {
Tabs.getInstance().selectTab(lastSelectedTabId);
}
restoreLastSelectedTab();
int newOrientation = getResources().getConfiguration().orientation;
if (GeckoScreenOrientation.getInstance().update(newOrientation)) {
@ -2296,6 +2294,12 @@ public abstract class GeckoApp
Restrictions.update(this);
}
protected void restoreLastSelectedTab() {
if (lastSelectedTabId >= 0 && (lastActiveGeckoApp == null || lastActiveGeckoApp.get() != this)) {
Tabs.getInstance().selectTab(lastSelectedTabId);
}
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);

Просмотреть файл

@ -6,12 +6,14 @@
package org.mozilla.gecko;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.customtabs.CustomTabsIntent;
import org.mozilla.gecko.webapps.WebAppActivity;
import org.mozilla.gecko.webapps.WebAppIndexer;
import org.mozilla.gecko.customtabs.CustomTabsActivity;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.mozglue.SafeIntent;
@ -89,11 +91,11 @@ public class LauncherActivity extends Activity {
}
private void dispatchWebAppIntent() {
Intent intent = new Intent(getIntent());
intent.setClassName(getApplicationContext(), WebAppActivity.class.getName());
filterFlags(intent);
final Intent intent = new Intent(getIntent());
final String manifestPath = getIntent().getStringExtra(WebAppActivity.MANIFEST_PATH);
final int index = WebAppIndexer.getInstance().getIndexForManifest(manifestPath, this);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClassName(this, WebAppIndexer.WEBAPP_CLASS + index);
startActivity(intent);
}

Просмотреть файл

@ -9,7 +9,9 @@ import java.io.File;
import java.io.IOException;
import android.app.ActivityManager;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.Window;
@ -25,7 +27,9 @@ 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.mozglue.SafeIntent;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.util.ColorUtil;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.FileUtils;
@ -41,11 +45,7 @@ public class WebAppActivity extends GeckoApp {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String manifestPath = getIntent().getStringExtra(WebAppActivity.MANIFEST_PATH);
if (manifestPath != null) {
updateFromManifest(manifestPath);
}
loadManifest(getIntent());
}
@Override
@ -53,6 +53,35 @@ public class WebAppActivity extends GeckoApp {
return R.layout.webapp_activity;
}
/**
* In case this activity is reused (the user has opened > 10 current web apps)
* we check that app launched is still within the same host as the
* shortcut has set, if not we reload the homescreens url
*/
@Override
protected void onNewIntent(Intent externalIntent) {
restoreLastSelectedTab();
final SafeIntent intent = new SafeIntent(externalIntent);
final String launchUrl = intent.getDataString();
final String currentUrl = Tabs.getInstance().getSelectedTab().getURL();
final boolean isSameDomain = Uri.parse(currentUrl).getHost()
.equals(Uri.parse(launchUrl).getHost());
if (!isSameDomain) {
loadManifest(externalIntent);
Tabs.getInstance().loadUrl(launchUrl);
}
}
private void loadManifest(Intent intent) {
String manifestPath = intent.getStringExtra(WebAppActivity.MANIFEST_PATH);
if (manifestPath != null) {
updateFromManifest(manifestPath);
}
}
private void updateFromManifest(String manifestPath) {
try {
final File manifestFile = new File(manifestPath);

Просмотреть файл

@ -0,0 +1,135 @@
/* -*- 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.util.ArrayList;
import android.content.Context;
import android.content.SharedPreferences;
import android.support.annotation.UiThread;
import org.mozilla.gecko.GeckoSharedPrefs;
/**
* WebAppIndexer lets us create bookmarks that behave like android applications
* we create 10 slots of WebAppN in the manifest, when a bookmark is launched
* we take its manifest and assign it an index, subsequent launches of that
* bookmark are given the same index and therefore reopen the previous activity.
* We limit this to 10 spaces and recycle the least recently used index once
* we hit the limit, this means if the user can actively use 10 webapps at the same
* time, more than that and the last used will be replaced
**/
public class WebAppIndexer {
public static final String WEBAPP_CLASS = "org.mozilla.gecko.webapps.WebApps$WebApp";
private final String mPrefNumSavedEntries = "WebAppIndexer.numActivities";
private final String mPrefActivityIndex = "WebAppIndexer.index";
private final String mPrefActivityId = "WebAppIndexer.manifest";
private static final int MAX_ACTIVITIES = 10;
private static final int INVALID_INDEX = -1;
private ArrayList<ActivityEntry> mActivityList = new ArrayList<ActivityEntry>();
private static class ActivityEntry {
public final int index;
public final String manifest;
ActivityEntry(int _index, String _manifest) {
index = _index;
manifest = _manifest;
}
}
private WebAppIndexer() { }
private final static WebAppIndexer INSTANCE = new WebAppIndexer();
public static WebAppIndexer getInstance() {
return INSTANCE;
}
public int getIndexForManifest(String manifest, Context context) {
if (mActivityList.size() == 0) {
loadActivityList(context);
}
int index = getManifestIndex(manifest);
// If we havent assigned this manifest an index then reassign the
// least recently used index
if (index == INVALID_INDEX) {
index = mActivityList.get(0).index;
final ActivityEntry newEntry = new ActivityEntry(index, manifest);
mActivityList.set(0, newEntry);
}
// Put the index at the back of the queue to be recycled
markActivityUsed(index, manifest, context);
return index;
}
private int getManifestIndex(String manifest) {
for (int i = mActivityList.size() - 1; i >= 0; i--) {
if (manifest.equals(mActivityList.get(i).manifest)) {
return mActivityList.get(i).index;
}
}
return INVALID_INDEX;
}
private void markActivityUsed(int index, String manifest, Context context) {
final int elementIndex = findActivityElement(index);
final ActivityEntry updatedEntry = new ActivityEntry(index, manifest);
mActivityList.remove(elementIndex);
mActivityList.add(updatedEntry);
storeActivityList(context);
}
private int findActivityElement(int index) {
for (int elementIndex = 0; elementIndex < mActivityList.size(); elementIndex++) {
if (mActivityList.get(elementIndex).index == index) {
return elementIndex;
}
}
return INVALID_INDEX;
}
// Store the list of assigned indexes in sharedPrefs because the LauncherActivity
// is likely to be killed and restarted between webapp launches
@UiThread
private void storeActivityList(Context context) {
final SharedPreferences prefs = GeckoSharedPrefs.forProfile(context);
final SharedPreferences.Editor editor = prefs.edit();
editor.clear();
editor.putInt(mPrefNumSavedEntries, mActivityList.size());
for (int i = 0; i < mActivityList.size(); ++i) {
editor.putInt(mPrefActivityIndex + i, mActivityList.get(i).index);
editor.putString(mPrefActivityId + i, mActivityList.get(i).manifest);
}
editor.apply();
}
@UiThread
private void loadActivityList(Context context) {
final SharedPreferences prefs = GeckoSharedPrefs.forProfile(context);
final int numSavedEntries = prefs.getInt(mPrefNumSavedEntries, 0);
for (int i = 0; i < numSavedEntries; ++i) {
int index = prefs.getInt(mPrefActivityIndex + i, i);
String manifest = prefs.getString(mPrefActivityId + i, null);
ActivityEntry entry = new ActivityEntry(index, manifest);
mActivityList.add(entry);
}
// Fill rest of the list with null spacers to be assigned
final int leftToFill = MAX_ACTIVITIES - mActivityList.size();
for (int i = 0; i < leftToFill; ++i) {
ActivityEntry entry = new ActivityEntry(i, null);
mActivityList.add(entry);
}
}
}

Просмотреть файл

@ -0,0 +1,25 @@
/* -*- 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;
/**
* 10 predefined slots for homescreen webapps, in LauncherActivity
* launched webapps will be given an index (via WebAppIndexer) that
* points to one of these class names
**/
public final class WebApps {
public static class WebApp0 extends WebAppActivity { }
public static class WebApp1 extends WebAppActivity { }
public static class WebApp2 extends WebAppActivity { }
public static class WebApp3 extends WebAppActivity { }
public static class WebApp4 extends WebAppActivity { }
public static class WebApp5 extends WebAppActivity { }
public static class WebApp6 extends WebAppActivity { }
public static class WebApp7 extends WebAppActivity { }
public static class WebApp8 extends WebAppActivity { }
public static class WebApp9 extends WebAppActivity { }
}

Просмотреть файл

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