Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(android): change app icons at runtime #14162

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions android/cli/commands/_build.js
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,9 @@ AndroidBuilder.prototype.validate = function validate(logger, config, cli) {
try {
if (cli.tiapp.android && cli.tiapp.android.manifest) {
this.customAndroidManifest = AndroidManifest.fromXmlString(cli.tiapp.android.manifest);

// check if we have <activity-alias> nodes
this.activityAliasCount = this.customAndroidManifest.xmlDomDocument.getElementsByTagName('application')[0].getElementsByTagName('activity-alias').length;
}
} catch (ex) {
logger.error(__n('Malformed <manifest> definition in the <android> section of the tiapp.xml'));
Expand Down Expand Up @@ -3522,7 +3525,8 @@ AndroidBuilder.prototype.fetchNeededManifestSettings = function fetchNeededManif
const neededSettings = {
queries: neededQueriesDictionary,
storagePermissionMaxSdkVersion: storagePermissionMaxSdkVersion,
usesPermissions: Object.keys(neededPermissionDictionary)
usesPermissions: Object.keys(neededPermissionDictionary),
skipLauncher: this.activityAliasCount > 0
};
return neededSettings;
};
Expand Down Expand Up @@ -3619,7 +3623,8 @@ AndroidBuilder.prototype.generateAndroidManifest = async function generateAndroi
storagePermissionMaxSdkVersion: neededManifestSettings.storagePermissionMaxSdkVersion,
packageName: this.appid,
queries: neededManifestSettings.queries,
usesPermissions: neededManifestSettings.usesPermissions
usesPermissions: neededManifestSettings.usesPermissions,
skipLauncher: this.activityAliasCount > 0
});
const mainManifest = AndroidManifest.fromXmlString(mainManifestContent);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/
package ti.modules.titanium.app;

import org.appcelerator.kroll.KrollDict;
import org.appcelerator.kroll.KrollModule;
import org.appcelerator.kroll.KrollRuntime;
import org.appcelerator.kroll.annotations.Kroll;
Expand All @@ -17,8 +18,10 @@
import org.appcelerator.titanium.proxy.RProxy;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;

@Kroll.module(parentModule = AppModule.class)
Expand Down Expand Up @@ -61,6 +64,27 @@ public ActivityProxy getTopActivity()
}
}

@Kroll.method
public void changeIcon(KrollDict options)
{
if (options.containsKeyAndNotNull("from") && options.containsKeyAndNotNull("to")) {
String oldPackage = options.getString("from");
String newPackage = options.getString("to");
String pkgName = TiApplication.getInstance().getPackageName();

TiApplication.getInstance().getPackageManager().setComponentEnabledSetting(
new ComponentName(pkgName, pkgName + "." + oldPackage),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP
);
TiApplication.getInstance().getPackageManager().setComponentEnabledSetting(
new ComponentName(pkgName, pkgName + "." + newPackage),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP
);
} else {
Log.e(TAG, "Parameters missing. Please provide 'from' and 'to'");
}
}

@Kroll.getProperty
public int getAppVersionCode()
{
Expand Down
2 changes: 2 additions & 0 deletions android/templates/build/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
android:alwaysRetainTaskState="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<% if (!skipLauncher) { %>
<category android:name="android.intent.category.LAUNCHER" />
<% } %>
</intent-filter>
</activity>

Expand Down
84 changes: 73 additions & 11 deletions apidoc/Titanium/App/Android/Android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,27 @@
name: Titanium.App.Android
summary: A module used to access Android application resources.
description: |
For more information, refer to the official documentation on the Android Developer website about
For more information, refer to the official documentation on the Android Developer website about
[application resources](https://developer.android.com/guide/topics/resources/index.html).
extends: Titanium.Module
since: "1.5"
platforms: [android]

methods:
- name: changeIcon
summary: Changes the Android app icon at runtime.
description: |
You have to add `<activity-alias>` nodes for every icon to the tiapp.xml and use `changeIcon()` to switch from one
to another. Check the the `Change Android icon at runtime` example for more details. The app has to keep track of
the current icon state in order to set the from/to parameters.
since: "12.7.0"
parameters:
- name: from
type: String
summary: <activity-alias> `android:name` (without leading dot) of the current icon that is going to be deactivated.
- name: to
type: String
summary: <activity-alias> `android:name` (without leading dot) of the icon that is going to be activated.
properties:
- name: R
summary: The `R` namespace for application resources.
Expand All @@ -33,14 +48,14 @@ properties:

- name: appVersionCode
summary: |
The version number of the application.
The version number of the application.
type: Number
permission: read-only
since: 3.3.0

- name: appVersionName
summary: |
The version name of the application.
The version name of the application.
type: String
permission: read-only
since: 3.3.0
Expand Down Expand Up @@ -76,24 +91,71 @@ events:
examples:
- title: Custom String Resource
example: |
Custom Android resources may be placed in `platform/android` in the project root.
For example, to utilize a custom localization file, create and populate

Custom Android resources may be placed in `platform/android` in the project root.
For example, to utilize a custom localization file, create and populate
`platform/android/res/values/mystrings.xml` with the following data.

``` xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="mystring">testing 1 2 3</string>
</resources>
```

In Javascript, this can be accessed as follows.

``` js
var activity = Ti.Android.currentActivity;
var R = Ti.App.Android.R;

var mystring = activity.getString(R.string.mystring);
Ti.API.debug("mystring = " + mystring);
```
- title: Change Android icon at runtime
example: |

Add these `<activity-alias>` nodes into the tiapp.xml `<application>` block:

``` xml
<activity-alias android:enabled="true" android:name=".default" android:targetActivity=".TestActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity-alias>
<activity-alias android:icon="@mipmap/ic_launcher" android:name=".red" android:enabled="false" android:targetActivity=".TestActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity-alias>
```

Change the `android:targetActivity` to point at your main activity, `android:name` is used inside the JavaScript code
to switch between the alias nodes and `android:icon` will be the new icon.

In Javascript, this can be accessed as follows.

``` js
const win = Ti.UI.createWindow({layout: "vertical"});

const btn1 = Ti.UI.createButton({title: "switch to red"});
const btn2 = Ti.UI.createButton({title: "switch to default"});

btn1.addEventListener("click", function() {
Ti.App.Android.changeIcon({
from: "default",
to: "red"
});
});
btn2.addEventListener("click", function() {
Ti.App.Android.changeIcon({
from: "red",
to: "default"
});
});

win.add([btn1, btn2]);
win.open();
```
Loading