Skip to main content
Version: 3.x

Fix Android Launch From Home

note

This documentation is a work in progress. It should suffice to fix this particular issue but additional changes are likely to be made.

Android Launch From Home

Cordova and Capacitor applications, when launched on an Android device, will startup and display the webview your application is served in. However, for Auth Connect this may not be the behavior you want when a user is on the login page: you will likely want your application to return to the login page.

To make this behavior possible you'll need to alter your Cordova or Capacitor application using the following instructions.

Cordova

There are a few steps to apply on a Cordova project.

  • Create a scripts folder in your project.
  • Create a file called LaunchActivity.java in the scripts folder and paste below in the contents.
  • Replace the line package io.ionic.starter with the id from your config.xml
package io.ionic.starter;

import android.content.Intent;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

public class LauncherActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent i = new Intent(this, MainActivity.class);
i.replaceExtras(this.getIntent());
startActivity(i);
finish();
}
}
  • Create a file called update-launcher.js in the scripts folder and paste the below file
  • Set the path of LAUNCHER_DESTINATION in update-launcher.js to match your id from config.xml
var ANDROID_PROJECT_ROOT = "platforms/android/app/src/main";
var LAUNCHER_DESTINATION =
"platforms/android/app/src/main/java/io/ionic/starter";
var LAUNCHER_SOURCE = "scripts/LauncherActivity.java";
var fs = require("fs");
var path = require("path");
var xml2js = require("xml2js");

function copyJavaLauncher() {
return new Promise((resolve, reject) => {
fs.access(LAUNCHER_DESTINATION, (err) => {
if (err) {
reject("INVALID LAUNCHER DESTINATION");
return;
}
copyFile(
LAUNCHER_SOURCE,
path.join(LAUNCHER_DESTINATION, "LauncherActivity.java")
);
});
});
}

function copyFile(src, dest) {
return new Promise((resolve, reject) => {
let readStream = fs.createReadStream(src);

readStream.once("error", (err) => {
reject(err);
});

readStream.once("end", () => {
resolve("done");
});

readStream.pipe(fs.createWriteStream(dest));
});
}

function readManifest() {
return new Promise((resolve, reject) => {
fs.readFile(
path.join(ANDROID_PROJECT_ROOT, "AndroidManifest.xml"),
"utf-8",
(err, input) => {
if (!!err) {
reject(err);
} else {
resolve(input);
}
}
);
});
}

function writeManifest(data) {
return new Promise((resolve, reject) => {
fs.writeFile(
path.join(ANDROID_PROJECT_ROOT, "AndroidManifest.xml"),
data,
(err) => {
if (!!err) {
reject(err);
} else {
resolve();
}
}
);
});
}

function convertToJson(input) {
return new Promise((resolve, reject) => {
xml2js.parseString(input, (err, result) => {
if (!!err) {
reject(err);
} else {
resolve(result);
}
});
});
}

function convertToXML(input) {
return new Promise((resolve, reject) => {
let builder = new xml2js.Builder();
let xml = builder.buildObject(input);
resolve(xml);
});
}

function removeLegacyActivityIntent(data) {
return new Promise((resolve, reject) => {
let applications = data.manifest.application;
if (!applications) {
reject();
return;
}
applications.forEach((application) => {
if (!!application.activity) {
application.activity.forEach((activity) => {
if (activity["intent-filter"]) {
activity["intent-filter"].forEach((intent, idx) => {
let shouldRemove = false;
if (intent.action) {
intent.action.forEach((action) => {
if (action["$"]["android:name"].includes("MAIN")) {
shouldRemove = true;
}
});
}
if (shouldRemove) {
delete activity["intent-filter"][idx];
}
});
}
});
}
});
resolve(data);
});
}

function addLauncherActivityIntent(data) {
return new Promise((resolve, reject) => {
let applications = data.manifest.application;
if (!applications) {
reject();
return;
}
applications.forEach((application) => {
if (typeof application.activity === "undefined") {
application.activity = [];
}
application.activity.push({
$: {
"android:name": "LauncherActivity",
"android:label": "@string/app_name",
"android:theme": "@android:style/Theme.DeviceDefault.NoActionBar",
},
"intent-filter": [
{
action: {
$: {
"android:name": "android.intent.action.MAIN",
},
},
category: {
$: {
"android:name": "android.intent.category.LAUNCHER",
},
},
},
],
});
});
resolve(data);
});
}

module.exports = function (context) {
return new Promise((resolve, reject) => {
readManifest()
.then((input) => convertToJson(input))
.then((data) => removeLegacyActivityIntent(data))
.then((data) => addLauncherActivityIntent(data))
.then((data) => convertToXML(data))
.then((input) => writeManifest(input))
.then(() => copyJavaLauncher())
.then((data) => {
resolve("done");
})
.catch((err) => {
console.log(err);
reject("done");
});
});
};
  • Next step is to install xml2js with:
npm install xml2js --save-dev

Finally update config.xml by adding the following line underneath <platform name="android">:

<hook src="scripts/update-launcher.js" type="after_platform_add" />

This script will run when you add the Android platform is added so to test it you can run: ionic cordova platform remove android ionic cordova platform add android

note

Be sure that the package line in LaunchActivity.java and the LAUNCHER_DESTINATION match what is used for your id in config.xml.

Capacitor

In your Capacitor project create a new activity with the following code replacing io.ionic.starter with your projects identifier.

package io.ionic.starter;

import android.content.Intent;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

public class LauncherActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent i = new Intent(this, MainActivity.class);
i.replaceExtras(this.getIntent());
startActivity(i);
finish();
}
}

Update the AndroidManifest.xml to set the new Activity as the launcher activity, and keep the MainActivity launchMode as singleTask

note

make sure to remove the intent-filter on the MainActivity that defines it as the launcher

<activity android:name="io.ionic.starter.LauncherActivity"
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBarLaunch">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
android:name="io.ionic.starter.MainActivity"
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBarLaunch"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="my-scheme" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/*"/>
</intent-filter>

</activity>

Be sure to run npx cap sync after applying these changes.

Testing

To test that you have correctly applied these changes:

  • Launch your application with an Android device
  • Go to the login page
  • Return to the home screen of your Android device
  • Launch your app again by pressing the icon
  • Your application should now open displaying the login page in the state it was left in