├── LICENSE ├── README.md ├── package.json ├── plugin.xml ├── shortcuts.d.ts ├── src └── android │ └── ShortcutsPlugin.java └── www └── ShortcutsPlugin.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Antonio Vargas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android Shortcuts Plugin for Cordova 2 | 3 | ## DESCRIPTION 4 | 5 | Use this plugin to create shortcuts in Android. Use this plugin to handle Intents on your application. 6 | 7 | For more information on Android App Shortcuts: https://developer.android.com/guide/topics/ui/shortcuts.html 8 | 9 | For more information on Android Intents: https://developer.android.com/guide/components/intents-filters.html 10 | 11 | The work that went into creating this plug-in was inspired by the existing plugins: [cordova-plugin-shortcut](https://github.com/jorgecis/ShortcutPlugin) and [cordova-plugin-webintent2](https://github.com/okwei2000/webintent). 12 | 13 | ## LICENSE 14 | 15 | The MIT License 16 | 17 | Copyright (c) 2013 Adobe Systems, inc. 18 | portions Copyright (c) 2013 Jorge Cisneros jorgecis@gmail.com 19 | 20 | Permission is hereby granted, free of charge, to any person obtaining a copy 21 | of this software and associated documentation files (the "Software"), to deal 22 | in the Software without restriction, including without limitation the rights 23 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 | copies of the Software, and to permit persons to whom the Software is 25 | furnished to do so, subject to the following conditions: 26 | 27 | The above copyright notice and this permission notice shall be included in 28 | all copies or substantial portions of the Software. 29 | 30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 36 | THE SOFTWARE. 37 | 38 | 39 | ## How to install it 40 | 41 | cordova plugins add cordova-plugin-shortcuts-android 42 | 43 | ## How to use it 44 | 45 | ### Checking if Dynamic Shortcuts are supported 46 | 47 | Dynamic shortcuts require SDK 25 or later. Use `supportsDynamic` to check whether the current device meets those requirements. 48 | 49 | ```javascript 50 | window.plugins.Shortcuts.supportsDynamic(function(supported) { 51 | if (supported) 52 | window.alert('Dynamic shortcuts are supported'); 53 | else 54 | window.alert('Dynamic shortcuts are NOT supported'); 55 | }, function(error) { 56 | window.alert('Error: ' + error); 57 | }) 58 | ``` 59 | 60 | ### Checking if Pinned Shortcuts are supported 61 | 62 | Pinned shortcuts require SDK 26 or later. Use `supportsPinned` to check whether the current device meets those requirements. 63 | 64 | ```javascript 65 | window.plugins.Shortcuts.supportsPinned(function(supported) { 66 | if (supported) 67 | window.alert('Pinned shortcuts are supported'); 68 | else 69 | window.alert('Pinned shortcuts are NOT supported'); 70 | }, function(error) { 71 | window.alert('Error: ' + error); 72 | }) 73 | ``` 74 | 75 | ### Setting the application Dynamic Shortcuts 76 | 77 | Use `setDynamic` to set the Dynamic Shortcuts for the application, all at once. The shortcuts provided as a parameter will override any existing shortcut. Use an empty array to clear out existing shortcuts. 78 | 79 | ```javascript 80 | var shortcut = { 81 | id: 'my_shortcut_1', 82 | shortLabel: 'Short description', 83 | longLabel: 'Longer string describing the shortcut', 84 | iconBitmap: '', 85 | iconFromResource: "ic_playlist_play_red", //filename w/o extension of an icon that resides on res/drawable-* (hdpi,mdpi..) 86 | intent: { 87 | action: 'android.intent.action.RUN', 88 | categories: [ 89 | 'android.intent.category.TEST', // Built-in Android category 90 | 'MY_CATEGORY' // Custom categories are also supported 91 | ], 92 | flags: 67108864, // FLAG_ACTIVITY_CLEAR_TOP 93 | data: 'myapp://path/to/launch?param=value', // Must be a well-formed URI 94 | extras: { 95 | 'android.intent.extra.SUBJECT': 'Hello world!', // Built-in Android extra (string) 96 | 'MY_BOOLEAN': true, // Custom extras are also supported (boolean, number and string only) 97 | } 98 | } 99 | } 100 | window.plugins.Shortcuts.setDynamic([shortcut], function() { 101 | window.alert('Shortcuts were applied successfully'); 102 | }, function(error) { 103 | window.alert('Error: ' + error); 104 | }) 105 | ``` 106 | 107 | ### Adding a Pinned Shortcut to the launcher 108 | 109 | Use `addPinned` to add a new Pinned Shortcut to the launcher. 110 | 111 | ```javascript 112 | var shortcut = { 113 | id: 'my_shortcut_1', 114 | shortLabel: 'Short description', 115 | longLabel: 'Longer string describing the shortcut', 116 | iconBitmap: '', // Defaults to the main application icon 117 | iconFromResource: "ic_playlist_play_red", //filename w/o extension of an icon that resides on res/drawable-* (hdpi,mdpi..) 118 | intent: { 119 | action: 'android.intent.action.RUN', 120 | categories: [ 121 | 'android.intent.category.TEST', // Built-in Android category 122 | 'MY_CATEGORY' // Custom categories are also supported 123 | ], 124 | flags: 67108864, // FLAG_ACTIVITY_CLEAR_TOP 125 | data: 'myapp://path/to/launch?param=value', // Must be a well-formed URI 126 | extras: { 127 | 'android.intent.extra.SUBJECT': 'Hello world!', // Built-in Android extra (string) 128 | 'MY_BOOLEAN': true, // Custom extras are also supported (boolean, number and string only) 129 | } 130 | } 131 | } 132 | window.plugins.Shortcuts.addPinned(shortcut, function() { 133 | window.alert('Shortcut pinned successfully'); 134 | }, function(error) { 135 | window.alert('Error: ' + error); 136 | }) 137 | ``` 138 | 139 | ### Querying current Intent 140 | 141 | Use `getIntent` to get the Intent that was used to launch the current instance of the Cordova activity. 142 | 143 | ```javascript 144 | window.plugins.Shortcuts.getIntent(function(intent) { 145 | window.alert(JSON.stringify(intent)); 146 | }) 147 | ``` 148 | 149 | ### Subscribe to new Intents 150 | 151 | Use `onNewIntent` to register a callback to be executed every time a new Intent is sent to your Cordova activity. Note that in some conditions this callback may not be executed. 152 | 153 | For more information see the documentation for [Activity.onNewIntent(Intent)](https://developer.android.com/reference/android/app/Activity.html#onNewIntent(android.content.Intent)) 154 | 155 | ```javascript 156 | window.plugins.Shortcuts.onNewIntent(function(intent) { 157 | window.alert(JSON.stringify(intent)); 158 | }) 159 | ``` 160 | 161 | Call with an empty callback to de-register the existing callback. 162 | 163 | ```javascript 164 | window.plugins.Shortcuts.onNewIntent(); // De-register existing callback 165 | ``` 166 | 167 | ## CHANGES 168 | 169 | * v0.1.2 BREAKING: Do not append package name to keys under `Intent.Extras` dictionary 170 | * v0.1.1 Support loading icons from drawable resources 171 | * v0.1.0 Original version -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-shortcuts-android", 3 | "version": "0.1.2", 4 | "description": "Cordova plugin for Android to create app shortcuts and handle intents.", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/avargaskun/cordova-plugin-shortcuts-android.git" 9 | }, 10 | "author": "avargaskun", 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/avargaskun/cordova-plugin-shortcuts-android/issues" 14 | }, 15 | "homepage": "https://github.com/avargaskun/cordova-plugin-shortcuts-android#readme", 16 | "keywords": [ 17 | "ecosystem:cordova", 18 | "cordova-android", 19 | "shortcut", 20 | "intent", 21 | "android" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | Android Shortcuts 8 | 9 | 10 | <p>This plugin lets you manage dynamic and pinned shortcuts for Android</p> 11 | 12 | 13 | MIT 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /shortcuts.d.ts: -------------------------------------------------------------------------------- 1 | interface Shortcut { 2 | id: string; 3 | shortLabel?: string; 4 | longLabel?: string; 5 | iconPath?: string; 6 | iconBitmap?: string; 7 | intent?: Intent; 8 | } 9 | 10 | interface Intent { 11 | activityClass?: string; // Defaults to currently running activity 12 | activityPackage?: string; // Defaults to currently running package 13 | action?: string; // Defaults to ACTION_VIEW 14 | flags?: number; // Defaults to FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_CLEAR_TOP 15 | categories?: string[]; 16 | data?: string; 17 | extras?: { [key: string]: any }; 18 | } 19 | 20 | interface Shortcuts { 21 | supportsDynamic(onSuccess?: (supported: boolean) => void, onError?: (error: any) => void); 22 | supportsPinned(onSuccess?: (supported: boolean) => void, onError?: (error: any) => void); 23 | setDynamic(shortcuts: Shortcut[], onSuccess?: () => void, onError?: (error: any) => void); 24 | addPinned(shortcut: Shortcut, onSuccess?: () => void, onError?: (error: any) => void); 25 | getIntent(onSuccess?: (intent: Intent) => void, onError?: (error: any) => void); 26 | onNewIntent(callback?: (intent: Intent) => void, onError?: (error: any) => void); 27 | } 28 | 29 | interface Plugins { 30 | Shortcuts: Shortcuts; 31 | } 32 | 33 | interface Window { 34 | plugins: Plugins; 35 | } -------------------------------------------------------------------------------- /src/android/ShortcutsPlugin.java: -------------------------------------------------------------------------------- 1 | package com.plugins.shortcuts; 2 | 3 | import java.security.InvalidParameterException; 4 | import java.util.Set; 5 | import java.util.ArrayList; 6 | import java.util.Iterator; 7 | 8 | import org.apache.cordova.CallbackContext; 9 | import org.apache.cordova.CordovaPlugin; 10 | import org.apache.cordova.PluginResult; 11 | import org.json.JSONObject; 12 | import org.json.JSONArray; 13 | import org.json.JSONException; 14 | import android.net.Uri; 15 | import android.util.Base64; 16 | import android.util.Log; 17 | import android.graphics.Bitmap; 18 | import android.graphics.BitmapFactory; 19 | import android.graphics.drawable.Icon; 20 | import android.content.ComponentName; 21 | import android.content.Context; 22 | import android.content.Intent; 23 | import android.os.Build; 24 | import android.os.Bundle; 25 | import android.content.pm.ApplicationInfo; 26 | import android.content.pm.PackageManager; 27 | import android.content.pm.ShortcutInfo; 28 | import android.content.pm.ShortcutManager; 29 | import android.content.res.Resources; 30 | import androidx.core.content.pm.ShortcutInfoCompat; 31 | import androidx.core.content.pm.ShortcutManagerCompat; 32 | import androidx.core.graphics.drawable.IconCompat; 33 | 34 | public class ShortcutsPlugin extends CordovaPlugin { 35 | 36 | private static final String TAG = "ShortcutsPlugin"; 37 | private static final String ACTION_SUPPORTS_DYNAMIC = "supportsDynamic"; 38 | private static final String ACTION_SUPPORTS_PINNED = "supportsPinned"; 39 | private static final String ACTION_SET_DYNAMIC = "setDynamic"; 40 | private static final String ACTION_ADD_PINNED = "addPinned"; 41 | private static final String ACTION_GET_INTENT = "getIntent"; 42 | private static final String ACTION_ON_NEW_INTENT = "onNewIntent"; 43 | 44 | private CallbackContext onNewIntentCallbackContext = null; 45 | 46 | @Override 47 | public boolean execute( 48 | String action, 49 | JSONArray args, 50 | CallbackContext callbackContext) { 51 | try { 52 | if (action.equals(ACTION_SUPPORTS_DYNAMIC)) { 53 | boolean supported = Build.VERSION.SDK_INT >= 25; 54 | callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, supported)); 55 | return true; 56 | } 57 | else if (action.equals(ACTION_SUPPORTS_PINNED)) { 58 | boolean supported = this.supportsPinned(); 59 | callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, supported)); 60 | return true; 61 | } 62 | else if (action.equals(ACTION_SET_DYNAMIC)) { 63 | setDynamicShortcuts(args); 64 | callbackContext.success(); 65 | return true; 66 | } else if (action.equals(ACTION_ADD_PINNED)) { 67 | boolean success = addPinnedShortcut(args); 68 | if (success) { 69 | callbackContext.success(); 70 | } else { 71 | callbackContext.error("Pinned shortcuts are not supported by the default launcher."); 72 | } 73 | } else if (action.equals(ACTION_GET_INTENT)) { 74 | getIntent(callbackContext); 75 | return true; 76 | } else if (action.equals(ACTION_ON_NEW_INTENT)) { 77 | subscribeOnNewIntent(args, callbackContext); 78 | return true; 79 | } 80 | } 81 | catch (Exception e) { 82 | Log.e(TAG, "Exception executing Cordova action: " + e.getMessage()); 83 | callbackContext.error(e.getMessage()); 84 | return true; 85 | } 86 | 87 | return false; 88 | } 89 | 90 | @Override 91 | public void onNewIntent( 92 | Intent intent 93 | ) { 94 | try { 95 | if (this.onNewIntentCallbackContext != null) { 96 | PluginResult result = new PluginResult(PluginResult.Status.OK, buildIntent(intent)); 97 | result.setKeepCallback(true); 98 | this.onNewIntentCallbackContext.sendPluginResult(result); 99 | } 100 | } catch (Exception e) { 101 | Log.e(TAG, "Exception handling onNewIntent: " + e.getMessage()); 102 | } 103 | } 104 | 105 | private boolean supportsPinned() { 106 | Context context = this.cordova.getActivity().getApplicationContext(); 107 | return ShortcutManagerCompat.isRequestPinShortcutSupported(context); 108 | } 109 | 110 | private void subscribeOnNewIntent( 111 | JSONArray args, 112 | CallbackContext callbackContext 113 | ) throws JSONException { 114 | boolean remove = args.optBoolean(0); 115 | if (remove) { 116 | this.onNewIntentCallbackContext = null; 117 | Log.i(TAG, "Removed callback for onNewIntent"); 118 | } 119 | else { 120 | this.onNewIntentCallbackContext = callbackContext; 121 | Log.i(TAG, "Added a new callback for onNewIntent"); 122 | } 123 | } 124 | 125 | private void getIntent(CallbackContext callbackContext) throws JSONException { 126 | Intent intent = this.cordova.getActivity().getIntent(); 127 | PluginResult result = new PluginResult(PluginResult.Status.OK, buildIntent(intent)); 128 | callbackContext.sendPluginResult(result); 129 | } 130 | 131 | private JSONObject buildIntent( 132 | Intent intent 133 | ) throws JSONException { 134 | JSONObject jsonIntent = new JSONObject(); 135 | 136 | jsonIntent.put("action", intent.getAction()); 137 | jsonIntent.put("flags", intent.getFlags()); 138 | 139 | Set categories = intent.getCategories(); 140 | if (categories != null) { 141 | jsonIntent.put("categories", new JSONArray(categories)); 142 | } 143 | 144 | Uri data = intent.getData(); 145 | if (data != null) { 146 | jsonIntent.put("data", data.toString()); 147 | } 148 | 149 | Bundle extras = intent.getExtras(); 150 | if (extras != null) { 151 | JSONObject jsonExtras = new JSONObject(); 152 | jsonIntent.put("extras", jsonExtras); 153 | Iterator keys = extras.keySet().iterator(); 154 | while (keys.hasNext()) { 155 | String key = keys.next(); 156 | Object value = extras.get(key); 157 | if (value instanceof Boolean) { 158 | jsonExtras.put(key, (Boolean)value); 159 | } 160 | else if (value instanceof Integer) { 161 | jsonExtras.put(key, (Integer)value); 162 | } 163 | else if (value instanceof Long) { 164 | jsonExtras.put(key, (Long)value); 165 | } 166 | else if (value instanceof Float) { 167 | jsonExtras.put(key, (Float)value); 168 | } 169 | else if (value instanceof Double) { 170 | jsonExtras.put(key, (Double)value); 171 | } 172 | else { 173 | jsonExtras.put(key, value.toString()); 174 | } 175 | } 176 | } 177 | 178 | return jsonIntent; 179 | } 180 | 181 | private Intent parseIntent( 182 | JSONObject jsonIntent 183 | ) throws JSONException { 184 | 185 | Intent intent = new Intent(); 186 | 187 | String activityClass = jsonIntent.optString( 188 | "activityClass", 189 | this.cordova.getActivity().getClass().getName()); 190 | String activityPackage = jsonIntent.optString( 191 | "activityPackage", 192 | this.cordova.getActivity().getPackageName()); 193 | intent.setClassName(activityPackage, activityClass); 194 | 195 | String action = jsonIntent.optString("action", Intent.ACTION_VIEW); 196 | if (action.indexOf('.') < 0) { 197 | action = activityPackage + '.' + action; 198 | } 199 | Log.i(TAG, "Creating new intent with action: " + action); 200 | intent.setAction(action); 201 | 202 | int flags = jsonIntent.optInt("flags", Intent.FLAG_ACTIVITY_NEW_TASK + Intent.FLAG_ACTIVITY_CLEAR_TOP); 203 | intent.setFlags(flags); // TODO: Support passing different flags 204 | 205 | JSONArray jsonCategories = jsonIntent.optJSONArray("categories"); 206 | if (jsonCategories != null) { 207 | int count = jsonCategories.length(); 208 | for (int i = 0; i < count; ++i) { 209 | String category = jsonCategories.getString(i); 210 | if (category.indexOf('.') < 0) { 211 | category = activityPackage + '.' + category; 212 | } 213 | intent.addCategory(category); 214 | } 215 | } 216 | 217 | String data = jsonIntent.optString("data"); 218 | if (data.length() > 0) { 219 | intent.setData(Uri.parse(data)); 220 | } 221 | 222 | JSONObject extras = jsonIntent.optJSONObject("extras"); 223 | if (extras != null) { 224 | Iterator keys = extras.keys(); 225 | while (keys.hasNext()) { 226 | String key = keys.next(); 227 | Object value = extras.get(key); 228 | if (value != null) { 229 | if (value instanceof Boolean) { 230 | intent.putExtra(key, (Boolean)value); 231 | } 232 | else if (value instanceof Integer) { 233 | intent.putExtra(key, (Integer)value); 234 | } 235 | else if (value instanceof Long) { 236 | intent.putExtra(key, (Long)value); 237 | } 238 | else if (value instanceof Float) { 239 | intent.putExtra(key, (Float)value); 240 | } 241 | else if (value instanceof Double) { 242 | intent.putExtra(key, (Double)value); 243 | } 244 | else { 245 | intent.putExtra(key, value.toString()); 246 | } 247 | } 248 | } 249 | } 250 | return intent; 251 | } 252 | 253 | private ShortcutInfo buildDynamicShortcut( 254 | JSONObject jsonShortcut) throws PackageManager.NameNotFoundException, JSONException { 255 | if (jsonShortcut == null) { 256 | throw new InvalidParameterException("Shortcut object cannot be null"); 257 | } 258 | 259 | Context context = this.cordova.getActivity().getApplicationContext(); 260 | String shortcutId = jsonShortcut.optString("id"); 261 | if (shortcutId.length() == 0) { 262 | throw new InvalidParameterException("A value for 'id' is required"); 263 | } 264 | 265 | ShortcutInfo.Builder builder = new ShortcutInfo.Builder(context, shortcutId); 266 | 267 | String shortLabel = jsonShortcut.optString("shortLabel"); 268 | String longLabel = jsonShortcut.optString("longLabel"); 269 | if (shortLabel.length() == 0 && longLabel.length() == 0) { 270 | throw new InvalidParameterException("A value for either 'shortLabel' or 'longLabel' is required"); 271 | } 272 | 273 | if (shortLabel.length() == 0) { 274 | shortLabel = longLabel; 275 | } 276 | 277 | if (longLabel.length() == 0) { 278 | longLabel = shortLabel; 279 | } 280 | 281 | Icon icon; 282 | String iconBitmap = jsonShortcut.optString("iconBitmap"); 283 | String iconFromResource = jsonShortcut.optString("iconFromResource"); 284 | 285 | String activityPackage = this.cordova.getActivity().getPackageName(); 286 | 287 | if (iconBitmap.length() > 0) { 288 | icon = Icon.createWithBitmap(decodeBase64Bitmap(iconBitmap)); 289 | } 290 | 291 | if (iconFromResource.length() > 0){ 292 | Resources activityRes = this.cordova.getActivity().getResources(); 293 | int iconId = activityRes.getIdentifier(iconFromResource, "drawable", activityPackage); 294 | icon = Icon.createWithResource(context, iconId); 295 | } 296 | 297 | else { 298 | PackageManager pm = context.getPackageManager(); 299 | ApplicationInfo applicationInfo = pm.getApplicationInfo(activityPackage, PackageManager.GET_META_DATA); 300 | icon = Icon.createWithResource(activityPackage, applicationInfo.icon); 301 | } 302 | 303 | JSONObject jsonIntent = jsonShortcut.optJSONObject("intent"); 304 | if (jsonIntent == null) { 305 | jsonIntent = new JSONObject(); 306 | } 307 | 308 | Intent intent = parseIntent(jsonIntent); 309 | 310 | return builder 311 | .setShortLabel(shortLabel) 312 | .setLongLabel(longLabel) 313 | .setIntent(intent) 314 | .setIcon(icon) 315 | .build(); 316 | } 317 | 318 | private void setDynamicShortcuts( 319 | JSONArray args) throws PackageManager.NameNotFoundException, JSONException { 320 | int count = args.length(); 321 | ArrayList shortcuts = new ArrayList(count); 322 | 323 | for (int i = 0; i < count; ++i) { 324 | shortcuts.add(buildDynamicShortcut(args.optJSONObject(i))); 325 | } 326 | 327 | Context context = this.cordova.getActivity().getApplicationContext(); 328 | ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class); 329 | shortcutManager.setDynamicShortcuts(shortcuts); 330 | 331 | Log.i(TAG, String.format("Saved %d dynamic shortcuts.", count)); 332 | } 333 | 334 | private ShortcutInfoCompat buildPinnedShortcut( 335 | JSONObject jsonShortcut 336 | ) throws PackageManager.NameNotFoundException, JSONException { 337 | if (jsonShortcut == null) { 338 | throw new InvalidParameterException("Parameters must include a valid shorcut."); 339 | } 340 | 341 | Context context = this.cordova.getActivity().getApplicationContext(); 342 | String shortcutId = jsonShortcut.optString("id"); 343 | if (shortcutId.length() == 0) { 344 | throw new InvalidParameterException("A value for 'id' is required"); 345 | } 346 | 347 | ShortcutInfoCompat.Builder builder = new ShortcutInfoCompat.Builder(context, shortcutId); 348 | 349 | String shortLabel = jsonShortcut.optString("shortLabel"); 350 | String longLabel = jsonShortcut.optString("longLabel"); 351 | if (shortLabel.length() == 0 && longLabel.length() == 0) { 352 | throw new InvalidParameterException("A value for either 'shortLabel' or 'longLabel' is required"); 353 | } 354 | 355 | if (shortLabel.length() == 0) { 356 | shortLabel = longLabel; 357 | } 358 | 359 | if (longLabel.length() == 0) { 360 | longLabel = shortLabel; 361 | } 362 | 363 | IconCompat icon; 364 | String iconBitmap = jsonShortcut.optString("iconBitmap"); 365 | 366 | if (iconBitmap.length() > 0) { 367 | icon = IconCompat.createWithBitmap(decodeBase64Bitmap(iconBitmap)); 368 | } 369 | else { 370 | String activityPackage = this.cordova.getActivity().getPackageName(); 371 | PackageManager pm = context.getPackageManager(); 372 | ApplicationInfo applicationInfo = pm.getApplicationInfo(activityPackage, PackageManager.GET_META_DATA); 373 | icon = IconCompat.createWithResource(context, applicationInfo.icon); 374 | } 375 | 376 | JSONObject jsonIntent = jsonShortcut.optJSONObject("intent"); 377 | if (jsonIntent == null) { 378 | jsonIntent = new JSONObject(); 379 | } 380 | 381 | Intent intent = parseIntent(jsonIntent); 382 | 383 | return builder 384 | .setActivity(intent.getComponent()) 385 | .setShortLabel(shortLabel) 386 | .setLongLabel(longLabel) 387 | .setIcon(icon) 388 | .setIntent(intent) 389 | .build(); 390 | } 391 | 392 | private boolean addPinnedShortcut( 393 | JSONArray args 394 | ) throws PackageManager.NameNotFoundException, JSONException { 395 | ShortcutInfoCompat shortcut = buildPinnedShortcut(args.optJSONObject(0)); 396 | Context context = this.cordova.getActivity().getApplicationContext(); 397 | return ShortcutManagerCompat.requestPinShortcut(context, shortcut, null); 398 | } 399 | 400 | private static Bitmap decodeBase64Bitmap( 401 | String input) { 402 | byte[] decodedByte = Base64.decode(input, 0); 403 | return BitmapFactory.decodeByteArray(decodedByte, 0, decodedByte.length); 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /www/ShortcutsPlugin.js: -------------------------------------------------------------------------------- 1 | var Shortcuts = function () { 2 | }; 3 | 4 | Shortcuts.prototype.supportsDynamic = function (successCallback, errorCallback) { 5 | cordova.exec( 6 | successCallback, 7 | errorCallback, 8 | 'ShortcutsPlugin', 9 | 'supportsDynamic', 10 | [] 11 | ); 12 | }; 13 | 14 | Shortcuts.prototype.supportsPinned = function (successCallback, errorCallback) { 15 | cordova.exec( 16 | successCallback, 17 | errorCallback, 18 | 'ShortcutsPlugin', 19 | 'supportsPinned', 20 | [] 21 | ); 22 | }; 23 | 24 | Shortcuts.prototype.setDynamic = function (shortcuts, successCallback, errorCallback) { 25 | cordova.exec( 26 | successCallback, 27 | errorCallback, 28 | 'ShortcutsPlugin', 29 | 'setDynamic', 30 | shortcuts 31 | ); 32 | }; 33 | 34 | Shortcuts.prototype.addPinned = function (shortcut, successCallback, errorCallback) { 35 | cordova.exec( 36 | successCallback, 37 | errorCallback, 38 | 'ShortcutsPlugin', 39 | 'addPinned', 40 | [shortcut] 41 | ); 42 | }; 43 | 44 | Shortcuts.prototype.getIntent = function (successCallback, errorCallback) { 45 | cordova.exec( 46 | successCallback, 47 | errorCallback, 48 | 'ShortcutsPlugin', 49 | 'getIntent', 50 | [] 51 | ) 52 | } 53 | 54 | Shortcuts.prototype.onNewIntent = function (callback, errorCallback) { 55 | cordova.exec( 56 | callback, 57 | errorCallback, 58 | 'ShortcutsPlugin', 59 | 'onNewIntent', 60 | [typeof (callback) !== 'function'] 61 | ) 62 | } 63 | 64 | if (!window.plugins) { 65 | window.plugins = {}; 66 | } 67 | 68 | if (!window.plugins.Shortcuts) { 69 | window.plugins.Shortcuts = new Shortcuts(); 70 | } --------------------------------------------------------------------------------