feat: multi icon support#2396
Conversation
Greptile SummaryThis PR adds multi-icon support for the Android launcher, allowing users to switch between the default Acode icon and an alternate icon (
Confidence Score: 3/5Not safe to merge — switching to the alternate icon disables The
Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant JS as plugin.js
participant Java as System.java
participant PM as PackageManager
JS->>Java: setAppIcon(".MainActivityAlt1")
Java->>PM: queryIntentActivities(MAIN/LAUNCHER)
PM-->>Java: [MainActivity, MainActivityAlt1]
Java->>Java: verify target exists
Java->>PM: setComponentEnabledSetting(MainActivityAlt1, ENABLED)
Java->>PM: setComponentEnabledSetting(MainActivity, DISABLED) ⚠️
Java-->>JS: callbackContext.success() [null, not boolean]
JS->>Java: getAppIcons()
Java->>PM: queryIntentActivities(MAIN/LAUNCHER, MATCH_DISABLED_COMPONENTS)
PM-->>Java: [MainActivity, MainActivityAlt1]
Java->>Java: build JSONArray with name/label/icon/enabled
Java-->>JS: success(JSONArray)
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant JS as plugin.js
participant Java as System.java
participant PM as PackageManager
JS->>Java: setAppIcon(".MainActivityAlt1")
Java->>PM: queryIntentActivities(MAIN/LAUNCHER)
PM-->>Java: [MainActivity, MainActivityAlt1]
Java->>Java: verify target exists
Java->>PM: setComponentEnabledSetting(MainActivityAlt1, ENABLED)
Java->>PM: setComponentEnabledSetting(MainActivity, DISABLED) ⚠️
Java-->>JS: callbackContext.success() [null, not boolean]
JS->>Java: getAppIcons()
Java->>PM: queryIntentActivities(MAIN/LAUNCHER, MATCH_DISABLED_COMPONENTS)
PM-->>Java: [MainActivity, MainActivityAlt1]
Java->>Java: build JSONArray with name/label/icon/enabled
Java-->>JS: success(JSONArray)
Reviews (1): Last reviewed commit: "feat: multi icon support" | Re-trigger Greptile |
| } else { | ||
| flags |= PackageManager.GET_DISABLED_COMPONENTS; | ||
| } | ||
|
|
||
| List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, flags); | ||
|
|
||
| String target = targetComponentName; | ||
| if (target.startsWith(".")) { | ||
| target = context.getPackageName() + target; | ||
| } | ||
|
|
||
| boolean found = false; | ||
|
|
||
| // First pass: verify if targetComponentName is a valid launcher component | ||
| for (ResolveInfo info : resolveInfos) { | ||
| if (info.activityInfo.name.equals(target)) { | ||
| found = true; | ||
| break; |
There was a problem hiding this comment.
setAppIcon disables MainActivity, making the app unlaunchable when an alias is active
The second pass iterates every entry returned by queryIntentActivities(MAIN/LAUNCHER) and disables all components that are not the target. MainActivity itself has a MAIN+LAUNCHER intent-filter (standard Cordova setup), so it appears in this list. When .MainActivityAlt1 is selected, MainActivity gets COMPONENT_ENABLED_STATE_DISABLED. Because an activity-alias always delegates to its targetActivity, Android requires the target to be enabled — a disabled MainActivity means MainActivityAlt1 can no longer launch the app, effectively making it unlaunchable from the home screen.
The standard pattern for icon switching is: (1) remove the MAIN/LAUNCHER intent-filter from MainActivity itself, (2) add a "default" activity-alias for the original icon, and (3) only ever toggle aliases — never touch MainActivity directly with setComponentEnabledSetting.
| * @param onSuccess | ||
| * @param onFail | ||
| */ | ||
| setAppIcon(componentName: string, onSuccess: OnSuccessBool, onFail: OnFail): void; |
There was a problem hiding this comment.
setAppIcon declares onSuccess: OnSuccessBool ((res: boolean) => void), but the Java implementation calls callbackContext.success() with no argument, so the callback always receives null. Any caller that checks res in the callback will always see null, not a boolean. The callback signature should match the no-arg void pattern used by similar write-only operations like setNativeContextMenuDisabled.
| setAppIcon(componentName: string, onSuccess: OnSuccessBool, onFail: OnFail): void; | |
| setAppIcon(componentName: string, onSuccess: () => void, onFail: OnFail): void; |
| case "get-app-info": | ||
| getAppInfo(callbackContext); | ||
| break; | ||
| case "get-app-icons": | ||
| getAppIcons(callbackContext); | ||
| break; | ||
| case "set-app-icon": | ||
| setAppIcon(arg1, callbackContext); | ||
| break; |
There was a problem hiding this comment.
The three new/modified
case blocks have an extra leading space compared to the surrounding case "pin-file-shortcut" and others (29 spaces vs 28). This creates an inconsistent indentation in an otherwise uniform switch block.
| case "get-app-info": | |
| getAppInfo(callbackContext); | |
| break; | |
| case "get-app-icons": | |
| getAppIcons(callbackContext); | |
| break; | |
| case "set-app-icon": | |
| setAppIcon(arg1, callbackContext); | |
| break; | |
| case "get-app-info": | |
| getAppInfo(callbackContext); | |
| break; | |
| case "get-app-icons": | |
| getAppIcons(callbackContext); | |
| break; | |
| case "set-app-icon": | |
| setAppIcon(arg1, callbackContext); | |
| break; |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
No description provided.