Commit b9faabca authored by Peter Kotwicz's avatar Peter Kotwicz Committed by Commit Bot

[Android WebAPK] Relaunch WebAPK when the user finishes first run experience

new-style WebAPKs have a different activity architecture than old-style
WebAPKs. new-style WebAPKs stack TransparentSplashWebApkActivity (which
runs in Chrome) on top of the SplashActivity (which runs in the WebAPK).
The SplashActivity keeps running till TransparentSplashWebApkActivity
finishes itself.

In order for tapping the app icon of an already running new-style
WebAPK to activate the already-running WebAPK instead of relaunching
the WebAPK, SplashActivity must be running.

This CL:
- Changes the first run experience to send an intent to relaunch the
WebAPK when the user completes the FRE. Previously,
TransparentSplashWebApkActivity was launched when the user completes
the FRE.
- Changes TransparentSplashWebApkActivity not to be singleTop. If
TransparentSplashWebApkActivity is singleTop,
FirstRunFlowSequencer#launch() triggers
TransparentSplashWebApkActivity#onNewIntent() when it relaunches the
activity with FLAG_ACTIVITY_NEW_TASK

BUG=911420

Change-Id: I1a0d648502e525ec133cfb2ef35c9fb2b5afca53
Reviewed-on: https://chromium-review.googlesource.com/c/1367187
Commit-Queue: Peter Kotwicz <pkotwicz@chromium.org>
Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Reviewed-by: default avatarYusuf Ozuysal <yusufo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#616119}
parent ca60fe10
...@@ -707,7 +707,6 @@ by a child template that "extends" this file. ...@@ -707,7 +707,6 @@ by a child template that "extends" this file.
<activity android:name="org.chromium.chrome.browser.webapps.TransparentSplashWebApkActivity" <activity android:name="org.chromium.chrome.browser.webapps.TransparentSplashWebApkActivity"
android:theme="@style/WebappTheme" android:theme="@style/WebappTheme"
android:label="@string/webapp_activity_title" android:label="@string/webapp_activity_title"
android:launchMode="singleTop"
android:exported="false" android:exported="false"
android:persistableMode="persistNever" android:persistableMode="persistNever"
{{ self.supports_video_persistence() }} {{ self.supports_video_persistence() }}
......
...@@ -269,8 +269,7 @@ public abstract class FirstRunFlowSequencer { ...@@ -269,8 +269,7 @@ public abstract class FirstRunFlowSequencer {
* @return A generic intent to show the First Run Activity. * @return A generic intent to show the First Run Activity.
* @param context The context. * @param context The context.
* @param fromIntent The intent that was used to launch Chrome. * @param fromIntent The intent that was used to launch Chrome.
* @param intentToLaunchAfterFreComplete The intent to relaunch Chrome when the user completes * @param intentToLaunchAfterFreComplete The intent to launch when the user completes the FRE.
* the FRE.
* @param requiresBroadcast Whether the relaunch intent must be broadcasted. * @param requiresBroadcast Whether the relaunch intent must be broadcasted.
*/ */
private static Intent createGenericFirstRunIntent(Context context, Intent fromIntent, private static Intent createGenericFirstRunIntent(Context context, Intent fromIntent,
...@@ -299,16 +298,20 @@ public abstract class FirstRunFlowSequencer { ...@@ -299,16 +298,20 @@ public abstract class FirstRunFlowSequencer {
* Returns an intent to show the lightweight first run activity. * Returns an intent to show the lightweight first run activity.
* @param context The context. * @param context The context.
* @param fromIntent The intent that was used to launch Chrome. * @param fromIntent The intent that was used to launch Chrome.
* @param intentToLaunchAfterFreComplete The intent to relaunch Chrome when the user completes * @param associatedAppName The id of the application associated with the activity
* the FRE. * being launched.
* @param intentToLaunchAfterFreComplete The intent to launch when the user completes the FRE.
* @param requiresBroadcast Whether the relaunch intent must be broadcasted. * @param requiresBroadcast Whether the relaunch intent must be broadcasted.
*/ */
private static Intent createLightweightFirstRunIntent(Context context, Intent fromIntent, private static Intent createLightweightFirstRunIntent(Context context, Intent fromIntent,
Intent intentToLaunchAfterFreComplete, boolean requiresBroadcast) { String associatedAppName, Intent intentToLaunchAfterFreComplete,
boolean requiresBroadcast) {
Intent intent = new Intent(); Intent intent = new Intent();
intent.setClassName(context, LightweightFirstRunActivity.class.getName()); intent.setClassName(context, LightweightFirstRunActivity.class.getName());
String appName = WebApkActivity.slowExtractNameFromIntentIfTargetIsWebApk(fromIntent); if (associatedAppName != null) {
intent.putExtra(LightweightFirstRunActivity.EXTRA_ASSOCIATED_APP_NAME, appName); intent.putExtra(
LightweightFirstRunActivity.EXTRA_ASSOCIATED_APP_NAME, associatedAppName);
}
addPendingIntent(context, intent, intentToLaunchAfterFreComplete, requiresBroadcast); addPendingIntent(context, intent, intentToLaunchAfterFreComplete, requiresBroadcast);
return intent; return intent;
} }
...@@ -317,21 +320,21 @@ public abstract class FirstRunFlowSequencer { ...@@ -317,21 +320,21 @@ public abstract class FirstRunFlowSequencer {
* Adds fromIntent as a PendingIntent to the firstRunIntent. This should be used to add a * Adds fromIntent as a PendingIntent to the firstRunIntent. This should be used to add a
* PendingIntent that will be sent when first run is completed. * PendingIntent that will be sent when first run is completed.
* *
* @param context The context that corresponds to the Intent. * @param context The context that corresponds to the Intent.
* @param firstRunIntent The intent that will be used to start first run. * @param firstRunIntent The intent that will be used to start first run.
* @param fromIntent The intent that was used to launch Chrome. * @param intentToLaunchAfterFreComplete The intent to launch when the user completes the FRE.
* @param requiresBroadcast Whether or not the fromIntent must be broadcasted. * @param requiresBroadcast Whether or not the fromIntent must be broadcasted.
*/ */
private static void addPendingIntent( private static void addPendingIntent(Context context, Intent firstRunIntent,
Context context, Intent firstRunIntent, Intent fromIntent, boolean requiresBroadcast) { Intent intentToLaunchAfterFreComplete, boolean requiresBroadcast) {
PendingIntent pendingIntent = null; PendingIntent pendingIntent = null;
int pendingIntentFlags = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT; int pendingIntentFlags = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT;
if (requiresBroadcast) { if (requiresBroadcast) {
pendingIntent = PendingIntent.getBroadcast( pendingIntent = PendingIntent.getBroadcast(context, FIRST_RUN_EXPERIENCE_REQUEST_CODE,
context, FIRST_RUN_EXPERIENCE_REQUEST_CODE, fromIntent, pendingIntentFlags); intentToLaunchAfterFreComplete, pendingIntentFlags);
} else { } else {
pendingIntent = PendingIntent.getActivity( pendingIntent = PendingIntent.getActivity(context, FIRST_RUN_EXPERIENCE_REQUEST_CODE,
context, FIRST_RUN_EXPERIENCE_REQUEST_CODE, fromIntent, pendingIntentFlags); intentToLaunchAfterFreComplete, pendingIntentFlags);
} }
firstRunIntent.putExtra(FirstRunActivity.EXTRA_FRE_COMPLETE_LAUNCH_INTENT, pendingIntent); firstRunIntent.putExtra(FirstRunActivity.EXTRA_FRE_COMPLETE_LAUNCH_INTENT, pendingIntent);
} }
...@@ -341,17 +344,17 @@ public abstract class FirstRunFlowSequencer { ...@@ -341,17 +344,17 @@ public abstract class FirstRunFlowSequencer {
* flags, we first relaunch it to make sure it runs in its own task, then trigger First Run. * flags, we first relaunch it to make sure it runs in its own task, then trigger First Run.
* *
* @param caller Activity instance that is checking if first run is necessary. * @param caller Activity instance that is checking if first run is necessary.
* @param intent Intent used to launch the caller. * @param fromIntent Intent used to launch the caller.
* @param requiresBroadcast Whether or not the Intent triggers a BroadcastReceiver. * @param requiresBroadcast Whether or not the Intent triggers a BroadcastReceiver.
* @param preferLightweightFre Whether to prefer the Lightweight First Run Experience. * @param preferLightweightFre Whether to prefer the Lightweight First Run Experience.
* @return Whether startup must be blocked (e.g. via Activity#finish or dropping the Intent). * @return Whether startup must be blocked (e.g. via Activity#finish or dropping the Intent).
*/ */
public static boolean launch(Context caller, Intent intent, boolean requiresBroadcast, public static boolean launch(Context caller, Intent fromIntent, boolean requiresBroadcast,
boolean preferLightweightFre) { boolean preferLightweightFre) {
// Check if the user needs to go through First Run at all. // Check if the user needs to go through First Run at all.
if (!checkIfFirstRunIsNecessary(caller, intent, preferLightweightFre)) return false; if (!checkIfFirstRunIsNecessary(caller, fromIntent, preferLightweightFre)) return false;
String intentUrl = IntentHandler.getUrlFromIntent(intent); String intentUrl = IntentHandler.getUrlFromIntent(fromIntent);
Uri uri = intentUrl != null ? Uri.parse(intentUrl) : null; Uri uri = intentUrl != null ? Uri.parse(intentUrl) : null;
if (uri != null && UrlConstants.CONTENT_SCHEME.equals(uri.getScheme())) { if (uri != null && UrlConstants.CONTENT_SCHEME.equals(uri.getScheme())) {
caller.grantUriPermission( caller.grantUriPermission(
...@@ -359,26 +362,39 @@ public abstract class FirstRunFlowSequencer { ...@@ -359,26 +362,39 @@ public abstract class FirstRunFlowSequencer {
} }
Log.d(TAG, "Redirecting user through FRE."); Log.d(TAG, "Redirecting user through FRE.");
if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { if ((fromIntent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
boolean isVrIntent = VrModuleProvider.getIntentDelegate().isVrIntent(intent); boolean isVrIntent = VrModuleProvider.getIntentDelegate().isVrIntent(fromIntent);
boolean isGenericFreActive = checkIsGenericFreActive(); boolean isGenericFreActive = checkIsGenericFreActive();
Intent intentToLaunchAfterFreComplete = fromIntent;
String associatedAppNameForLightweightFre = null;
WebApkActivity.FreParams webApkFreParams =
WebApkActivity.slowGenerateFreParamsIfIntentIsForWebApkActivity(fromIntent);
if (webApkFreParams != null) {
intentToLaunchAfterFreComplete =
webApkFreParams.getIntentToLaunchAfterFreComplete();
associatedAppNameForLightweightFre = webApkFreParams.webApkShortName();
}
// Launch the Generic First Run Experience if it was previously active. // Launch the Generic First Run Experience if it was previously active.
Intent freIntent = null; Intent freIntent = null;
if (preferLightweightFre && !isGenericFreActive) { if (preferLightweightFre && !isGenericFreActive) {
freIntent = freIntent = createLightweightFirstRunIntent(caller, fromIntent,
createLightweightFirstRunIntent(caller, intent, intent, requiresBroadcast); associatedAppNameForLightweightFre, intentToLaunchAfterFreComplete,
requiresBroadcast);
} else { } else {
freIntent = createGenericFirstRunIntent(caller, intent, intent, requiresBroadcast); freIntent = createGenericFirstRunIntent(
caller, fromIntent, intentToLaunchAfterFreComplete, requiresBroadcast);
if (shouldSwitchToTabbedMode(caller)) { if (shouldSwitchToTabbedMode(caller)) {
freIntent.setClass(caller, TabbedModeFirstRunActivity.class); freIntent.setClass(caller, TabbedModeFirstRunActivity.class);
// We switched to TabbedModeFRE. We need to disable animation on the original // We switched to TabbedModeFRE. We need to disable animation on the original
// intent, to make transition seamless. // intent, to make transition seamless.
intent = new Intent(intent); intentToLaunchAfterFreComplete = new Intent(intentToLaunchAfterFreComplete);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); intentToLaunchAfterFreComplete.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
addPendingIntent(caller, freIntent, intent, requiresBroadcast); addPendingIntent(
caller, freIntent, intentToLaunchAfterFreComplete, requiresBroadcast);
} }
} }
...@@ -391,7 +407,7 @@ public abstract class FirstRunFlowSequencer { ...@@ -391,7 +407,7 @@ public abstract class FirstRunFlowSequencer {
} else { } else {
// First Run requires that the Intent contains NEW_TASK so that it doesn't sit on top // First Run requires that the Intent contains NEW_TASK so that it doesn't sit on top
// of something else. // of something else.
Intent newIntent = new Intent(intent); Intent newIntent = new Intent(fromIntent);
newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
IntentUtils.safeStartActivity(caller, newIntent); IntentUtils.safeStartActivity(caller, newIntent);
} }
......
...@@ -39,18 +39,47 @@ public class WebApkActivity extends WebappActivity { ...@@ -39,18 +39,47 @@ public class WebApkActivity extends WebappActivity {
@VisibleForTesting @VisibleForTesting
public static final String STARTUP_UMA_HISTOGRAM_SUFFIX = ".WebApk"; public static final String STARTUP_UMA_HISTOGRAM_SUFFIX = ".WebApk";
/** WebAPK first run experience parameters. */
public static class FreParams {
private final Intent mIntentToLaunchAfterFreComplete;
private final String mShortName;
public FreParams(Intent intentToLaunchAfterFreComplete, String shortName) {
mIntentToLaunchAfterFreComplete = intentToLaunchAfterFreComplete;
mShortName = shortName;
}
/** Returns the intent launch when the user completes the first run experience. */
public Intent getIntentToLaunchAfterFreComplete() {
return mIntentToLaunchAfterFreComplete;
}
/** Returns the WebAPK's short name. */
public String webApkShortName() {
return mShortName;
}
}
/** /**
* Tries extracting the WebAPK short name from the passed in intent. Returns null if the intent * Generates parameters for the WebAPK first run experience for the given intent. Returns null
* does not launch a WebApkActivity. This method is slow. It makes several PackageManager calls. * if the intent does not launch a WebApkActivity. This method is slow. It makes several
* PackageManager calls.
*/ */
public static String slowExtractNameFromIntentIfTargetIsWebApk(Intent intent) { public static FreParams slowGenerateFreParamsIfIntentIsForWebApkActivity(Intent fromIntent) {
// Check for intents targetted at WebApkActivity and WebApkActivity0-9. // Check for intents targeted at WebApkActivity, WebApkActivity0-9 and
if (!intent.getComponent().getClassName().startsWith(WebApkActivity.class.getName())) { // TransparentSplashWebApkActivity.
String targetActivityClassName = fromIntent.getComponent().getClassName();
if (!targetActivityClassName.startsWith(WebApkActivity.class.getName())
&& !targetActivityClassName.equals(
TransparentSplashWebApkActivity.class.getName())) {
return null; return null;
} }
WebApkInfo info = WebApkInfo.create(intent); WebApkInfo info = WebApkInfo.create(fromIntent);
return (info != null) ? info.shortName() : null; return (info != null)
? new FreParams(WebappLauncherActivity.createRelaunchWebApkIntent(fromIntent, info),
info.shortName())
: null;
} }
@Override @Override
......
...@@ -54,6 +54,45 @@ public class WebappLauncherActivity extends Activity { ...@@ -54,6 +54,45 @@ public class WebappLauncherActivity extends Activity {
private static final String TAG = "webapps"; private static final String TAG = "webapps";
/** Creates intent to relaunch WebAPK. */
public static Intent createRelaunchWebApkIntent(Intent sourceIntent, WebApkInfo webApkInfo) {
assert webApkInfo != null;
Intent intent = new Intent(Intent.ACTION_VIEW, webApkInfo.uri());
intent.setPackage(webApkInfo.webApkPackageName());
intent.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | ApiCompatibilityUtils.getActivityNewDocumentFlag());
Bundle extras = sourceIntent.getExtras();
if (extras != null) {
intent.putExtras(extras);
}
return intent;
}
/**
* Brings a live WebappActivity back to the foreground if one exists for the given tab ID.
* @param tabId ID of the Tab to bring back to the foreground.
* @return True if a live WebappActivity was found, false otherwise.
*/
public static boolean bringWebappToFront(int tabId) {
if (tabId == Tab.INVALID_TAB_ID) return false;
for (WeakReference<Activity> activityRef : ApplicationStatus.getRunningActivities()) {
Activity activity = activityRef.get();
if (activity == null || !(activity instanceof WebappActivity)) continue;
WebappActivity webappActivity = (WebappActivity) activity;
if (webappActivity.getActivityTab() != null
&& webappActivity.getActivityTab().getId() == tabId) {
Tab tab = webappActivity.getActivityTab();
tab.getTabWebContentsDelegateAndroid().activateContents();
return true;
}
}
return false;
}
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
...@@ -157,15 +196,7 @@ public class WebappLauncherActivity extends Activity { ...@@ -157,15 +196,7 @@ public class WebappLauncherActivity extends Activity {
/** Relaunches WebAPK. */ /** Relaunches WebAPK. */
private static void relaunchWebApk( private static void relaunchWebApk(
Activity launchingActivity, Intent sourceIntent, @NonNull WebappInfo info) { Activity launchingActivity, Intent sourceIntent, @NonNull WebappInfo info) {
Intent launchIntent = new Intent(Intent.ACTION_VIEW, info.uri()); Intent launchIntent = createRelaunchWebApkIntent(sourceIntent, (WebApkInfo) info);
launchIntent.setPackage(info.webApkPackageName());
launchIntent.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | ApiCompatibilityUtils.getActivityNewDocumentFlag());
Bundle extras = sourceIntent.getExtras();
if (extras != null) {
launchIntent.putExtras(extras);
}
launchAfterDelay( launchAfterDelay(
launchingActivity.getApplicationContext(), launchIntent, WEBAPK_LAUNCH_DELAY_MS); launchingActivity.getApplicationContext(), launchIntent, WEBAPK_LAUNCH_DELAY_MS);
ApiCompatibilityUtils.finishAndRemoveTask(launchingActivity); ApiCompatibilityUtils.finishAndRemoveTask(launchingActivity);
...@@ -287,30 +318,6 @@ public class WebappLauncherActivity extends Activity { ...@@ -287,30 +318,6 @@ public class WebappLauncherActivity extends Activity {
return launchIntent; return launchIntent;
} }
/**
* Brings a live WebappActivity back to the foreground if one exists for the given tab ID.
* @param tabId ID of the Tab to bring back to the foreground.
* @return True if a live WebappActivity was found, false otherwise.
*/
public static boolean bringWebappToFront(int tabId) {
if (tabId == Tab.INVALID_TAB_ID) return false;
for (WeakReference<Activity> activityRef : ApplicationStatus.getRunningActivities()) {
Activity activity = activityRef.get();
if (activity == null || !(activity instanceof WebappActivity)) continue;
WebappActivity webappActivity = (WebappActivity) activity;
if (webappActivity.getActivityTab() != null
&& webappActivity.getActivityTab().getId() == tabId) {
Tab tab = webappActivity.getActivityTab();
tab.getTabWebContentsDelegateAndroid().activateContents();
return true;
}
}
return false;
}
/** Tries to create WebappInfo/WebApkInfo for the intent. */ /** Tries to create WebappInfo/WebApkInfo for the intent. */
private static WebappInfo tryCreateWebappInfo(Intent intent) { private static WebappInfo tryCreateWebappInfo(Intent intent) {
// Builds WebApkInfo for the intent if the WebAPK package specified in the intent is a valid // Builds WebApkInfo for the intent if the WebAPK package specified in the intent is a valid
......
...@@ -5,9 +5,11 @@ ...@@ -5,9 +5,11 @@
package org.chromium.chrome.browser.firstrun; package org.chromium.chrome.browser.firstrun;
import android.app.Activity; import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle;
import android.os.UserManager; import android.os.UserManager;
import android.support.customtabs.CustomTabsIntent; import android.support.customtabs.CustomTabsIntent;
...@@ -21,13 +23,23 @@ import org.mockito.junit.MockitoJUnit; ...@@ -21,13 +23,23 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule; import org.mockito.junit.MockitoRule;
import org.robolectric.Robolectric; import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication; import org.robolectric.shadows.ShadowApplication;
import org.chromium.base.test.BaseRobolectricTestRunner; import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.ChromeTabbedActivity; import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.ShortcutHelper;
import org.chromium.chrome.browser.document.ChromeLauncherActivity; import org.chromium.chrome.browser.document.ChromeLauncherActivity;
import org.chromium.chrome.browser.searchwidget.SearchActivity; import org.chromium.chrome.browser.searchwidget.SearchActivity;
import org.chromium.chrome.browser.webapps.TransparentSplashWebApkActivity;
import org.chromium.chrome.browser.webapps.WebApkActivity;
import org.chromium.chrome.browser.webapps.WebApkActivity0;
import org.chromium.chrome.browser.webapps.WebappLauncherActivity;
import org.chromium.webapk.lib.client.WebApkValidator;
import org.chromium.webapk.lib.common.WebApkConstants;
import org.chromium.webapk.lib.common.WebApkMetaDataKeys;
import org.chromium.webapk.test.WebApkTestHelper;
/** JUnit tests for first run triggering code. */ /** JUnit tests for first run triggering code. */
@RunWith(BaseRobolectricTestRunner.class) @RunWith(BaseRobolectricTestRunner.class)
...@@ -49,6 +61,40 @@ public final class FirstRunIntegrationUnitTest { ...@@ -49,6 +61,40 @@ public final class FirstRunIntegrationUnitTest {
mShadowApplication.setSystemService(Context.USER_SERVICE, userManager); mShadowApplication.setSystemService(Context.USER_SERVICE, userManager);
FirstRunStatus.setFirstRunFlowComplete(false); FirstRunStatus.setFirstRunFlowComplete(false);
WebApkValidator.disableValidationForTesting();
}
/** Checks that the intent component is one of the provided classes. */
private boolean checkIntentComponentClassOneOf(Intent intent, Class[] componentClassOptions) {
if (intent == null || intent.getComponent() == null) return false;
String componentClassName = intent.getComponent().getClassName();
for (Class componentClassOption : componentClassOptions) {
if (componentClassOption.getName().equals(componentClassName)) return true;
}
return false;
}
/**
* Checks that intent is either for {@link FirstRunActivity} or
* {@link TabbedModeFirstRunActivity}.
*/
private boolean checkIntentIsForFre(Intent intent) {
return checkIntentComponentClassOneOf(
intent, new Class[] {FirstRunActivity.class, TabbedModeFirstRunActivity.class});
}
/** Builds activity using the component class name from the provided intent. */
@SuppressWarnings("unchecked")
private static void buildActivityWithClassNameFromIntent(Intent intent) {
Class<? extends Activity> activityClass = null;
try {
activityClass =
(Class<? extends Activity>) Class.forName(intent.getComponent().getClassName());
} catch (ClassNotFoundException e) {
Assert.fail();
}
Robolectric.buildActivity(activityClass, intent).create();
} }
/** /**
...@@ -59,9 +105,7 @@ public final class FirstRunIntegrationUnitTest { ...@@ -59,9 +105,7 @@ public final class FirstRunIntegrationUnitTest {
Intent launchedIntent = mShadowApplication.getNextStartedActivity(); Intent launchedIntent = mShadowApplication.getNextStartedActivity();
Assert.assertNotNull(launchedIntent); Assert.assertNotNull(launchedIntent);
String launchedActivityClassName = launchedIntent.getComponent().getClassName(); Assert.assertTrue(checkIntentIsForFre(launchedIntent));
Assert.assertTrue(launchedActivityClassName.equals(FirstRunActivity.class.getName())
|| launchedActivityClassName.equals(TabbedModeFirstRunActivity.class.getName()));
} }
@Test @Test
...@@ -111,4 +155,43 @@ public final class FirstRunIntegrationUnitTest { ...@@ -111,4 +155,43 @@ public final class FirstRunIntegrationUnitTest {
assertFirstRunActivityLaunched(); assertFirstRunActivityLaunched();
Assert.assertTrue(searchActivity.isFinishing()); Assert.assertTrue(searchActivity.isFinishing());
} }
/**
* Tests that when the first run experience is shown by a WebAPK that the WebAPK is launched
* when the user finishes the first run experience. In the case where the WebAPK (as opposed
* to WebApkActivity) displays the splash screen this is necessary for correct behaviour when
* the user taps the app icon and the WebAPK is still running.
*/
@Test
public void testFreRelaunchesWebApkNotWebApkActivity() {
String webApkPackageName = "org.chromium.webapk.name";
String startUrl = "https://pwa.rocks/";
Bundle bundle = new Bundle();
bundle.putString(WebApkMetaDataKeys.START_URL, startUrl);
WebApkTestHelper.registerWebApkWithMetaData(webApkPackageName, bundle);
WebApkTestHelper.addIntentFilterForUrl(webApkPackageName, startUrl);
Intent intent = new Intent();
intent.putExtra(WebApkConstants.EXTRA_WEBAPK_PACKAGE_NAME, webApkPackageName);
intent.putExtra(ShortcutHelper.EXTRA_URL, startUrl);
intent.putExtra(WebApkConstants.EXTRA_USE_TRANSPARENT_SPLASH, true);
Robolectric.buildActivity(WebappLauncherActivity.class, intent).create();
Intent launchedIntent = mShadowApplication.getNextStartedActivity();
while (checkIntentComponentClassOneOf(launchedIntent,
new Class[] {WebApkActivity.class, WebApkActivity0.class,
TransparentSplashWebApkActivity.class})) {
buildActivityWithClassNameFromIntent(launchedIntent);
launchedIntent = mShadowApplication.getNextStartedActivity();
}
Assert.assertTrue(checkIntentIsForFre(launchedIntent));
PendingIntent freCompleteLaunchIntent = launchedIntent.getParcelableExtra(
FirstRunActivityBase.EXTRA_FRE_COMPLETE_LAUNCH_INTENT);
Assert.assertNotNull(freCompleteLaunchIntent);
Assert.assertEquals(webApkPackageName,
Shadows.shadowOf(freCompleteLaunchIntent).getSavedIntent().getPackage());
}
} }
...@@ -4,8 +4,11 @@ ...@@ -4,8 +4,11 @@
package org.chromium.webapk.test; package org.chromium.webapk.test;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.ResolveInfo;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Bundle; import android.os.Bundle;
...@@ -14,11 +17,12 @@ import org.robolectric.RuntimeEnvironment; ...@@ -14,11 +17,12 @@ import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows; import org.robolectric.Shadows;
import org.robolectric.shadows.ShadowPackageManager; import org.robolectric.shadows.ShadowPackageManager;
import java.net.URISyntaxException;
/** /**
* Helper class for WebAPK JUnit tests. * Helper class for WebAPK JUnit tests.
*/ */
public class WebApkTestHelper { public class WebApkTestHelper {
/** /**
* Registers WebAPK. This function also creates an empty resource for the WebAPK. * Registers WebAPK. This function also creates an empty resource for the WebAPK.
* @param packageName The package to register * @param packageName The package to register
...@@ -32,6 +36,19 @@ public class WebApkTestHelper { ...@@ -32,6 +36,19 @@ public class WebApkTestHelper {
packageManager.addPackage(newPackageInfo(packageName, metaData)); packageManager.addPackage(newPackageInfo(packageName, metaData));
} }
/** Registers intent filter for the passed-in package name and URL. */
public static void addIntentFilterForUrl(String packageName, String url) {
ShadowPackageManager packageManager =
Shadows.shadowOf(RuntimeEnvironment.application.getPackageManager());
try {
Intent deepLinkIntent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
deepLinkIntent.addCategory(Intent.CATEGORY_BROWSABLE);
deepLinkIntent.setPackage(packageName);
packageManager.addResolveInfoForIntent(deepLinkIntent, newResolveInfo(packageName));
} catch (URISyntaxException e) {
}
}
/** Sets the resource for the given package name. */ /** Sets the resource for the given package name. */
public static void setResource(String packageName, Resources res) { public static void setResource(String packageName, Resources res) {
ShadowPackageManager packageManager = ShadowPackageManager packageManager =
...@@ -47,4 +64,12 @@ public class WebApkTestHelper { ...@@ -47,4 +64,12 @@ public class WebApkTestHelper {
packageInfo.applicationInfo = applicationInfo; packageInfo.applicationInfo = applicationInfo;
return packageInfo; return packageInfo;
} }
private static ResolveInfo newResolveInfo(String packageName) {
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.packageName = packageName;
ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.activityInfo = activityInfo;
return resolveInfo;
}
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment