UNPKG

17.5 kBtext/x-java-sourceView Raw
1package com.ionicframework.common;
2
3import android.content.Context;
4import android.content.SharedPreferences;
5import android.content.res.AssetManager;
6import android.net.Uri;
7
8import org.apache.commons.io.FileUtils;
9import org.apache.cordova.CordovaWebView;
10import org.apache.cordova.CallbackContext;
11import org.apache.cordova.CordovaPlugin;
12import org.apache.cordova.CordovaInterface;
13import org.apache.cordova.PluginResult;
14import org.json.JSONArray;
15import org.json.JSONObject;
16import org.json.JSONException;
17
18import android.os.Environment;
19import android.util.Log;
20import android.app.Activity;
21import android.content.pm.PackageInfo;
22import android.os.Build;
23
24import java.io.DataInputStream;
25import java.io.File;
26import java.io.FileOutputStream;
27import java.io.IOException;
28import java.io.InputStream;
29import java.io.OutputStream;
30import java.net.URL;
31import java.util.Iterator;
32import java.util.UUID;
33
34public class IonicCordovaCommon extends CordovaPlugin {
35 public static final String TAG = "IonicCordovaCommon";
36 private static final String PREFS_KEY = "ionicDeploySavedPreferences";
37 private static final String CUSTOM_PREFS_KEY = "ionicDeployCustomPreferences";
38 private AssetManager assetManager;
39
40
41 private SharedPreferences prefs;
42 private String uuid;
43
44 private interface FileOp {
45 void run(final JSONArray args, final CallbackContext callbackContext) throws Exception;
46 }
47
48 /**
49 * Sets the context of the Command. This can then be used to do things like
50 * get file paths associated with the Activity.
51 *
52 * @param cordova The context of the main Activity.
53 * @param webView The CordovaWebView Cordova is running in.
54 */
55 public void initialize(CordovaInterface cordova, CordovaWebView webView) {
56 super.initialize(cordova, webView);
57
58 // Initialize shared preferences
59 Context cxt = this.cordova.getActivity().getApplicationContext();
60 this.prefs = cxt.getSharedPreferences("com.ionic.common.preferences", Context.MODE_PRIVATE);
61 assetManager = cordova.getContext().getAssets();
62
63 // Get or generate a plugin UUID
64 this.uuid = this.prefs.getString("uuid", UUID.randomUUID().toString());
65 prefs.edit().putString("uuid", this.uuid).apply();
66 }
67
68 private void threadhelper(final FileOp f, final JSONArray args, final CallbackContext callbackContext){
69 cordova.getThreadPool().execute(new Runnable() {
70 public void run() {
71 try {
72 f.run(args, callbackContext);
73 } catch ( Exception e) {
74 callbackContext.error(e.getMessage());
75 }
76 }
77 });
78 }
79
80 /**
81 * Executes the request and returns PluginResult.
82 *
83 * @param action The action to execute.
84 * @param args JSONArray of arguments for the plugin.
85 * @param callbackContext The callback id used when calling back into JavaScript.
86 * @return True if the action was valid, false if not.
87 */
88 public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
89 if (action.equals("getAppInfo")) {
90 this.getAppInfo(callbackContext);
91 } else if (action.equals("getPreferences")) {
92 this.getPreferences(callbackContext);
93 } else if (action.equals("setPreferences")) {
94 this.setPreferences(callbackContext, args.getJSONObject(0));
95 } else if (action.equals("configure")){
96 this.configure(callbackContext, args.getJSONObject(0));
97 } else if (action.equals("copyTo")){
98 this.copyTo(callbackContext, args.getJSONObject(0));
99 } else if (action.equals("remove")){
100 this.remove(callbackContext, args.getJSONObject(0));
101 } else if (action.equals("downloadFile")){
102 threadhelper( new FileOp( ){
103 public void run(final JSONArray passedArgs, final CallbackContext cbcontext) throws JSONException {
104 downloadFile(cbcontext, passedArgs.getJSONObject(0));
105 }
106 }, args, callbackContext);
107
108 } else {
109 return false;
110 }
111
112 return true;
113 }
114
115 private File getDirectory(String directory) {
116 Context c = cordova.getContext();
117 switch(directory) {
118 case "DOCUMENTS":
119 return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
120 case "DATA":
121 return c.getFilesDir();
122 case "CACHE":
123 return c.getCacheDir();
124 case "EXTERNAL":
125 return c.getExternalFilesDir(null);
126 case "EXTERNAL_STORAGE":
127 return Environment.getExternalStorageDirectory();
128 }
129 return null;
130 }
131
132 private void copyAssets(String assetPath, String targetDir) throws IOException {
133 String[] files = null;
134 try {
135 files = assetManager.list(assetPath);
136 } catch (IOException e) {
137 Log.e("tag", "Failed to get asset file list.", e);
138 }
139 if (files != null) for (String filename : files) {
140 InputStream in = null;
141 OutputStream out = null;
142 try {
143 if (assetManager.list(assetPath + "/" + filename).length > 0) {
144 File newDir = new File(targetDir, filename);
145 newDir.mkdir();
146 copyAssets(assetPath + "/" + filename, newDir.getPath());
147 continue;
148 }
149 in = assetManager.open(assetPath + "/" + filename);
150 File destDir = new File(targetDir);
151 if (!destDir.exists()) {
152 destDir.mkdirs();
153 }
154 File outFile = new File(targetDir, filename);
155 out = new FileOutputStream(outFile);
156 copyFile(in, out);
157 } catch(IOException e) {
158 Log.e("tag", "Failed to copy asset file: " + filename, e);
159 }
160 finally {
161 if (in != null) {
162 try {
163 in.close();
164 } catch (IOException e) {
165 // NOOP
166 }
167 }
168 if (out != null) {
169 try {
170 out.close();
171 } catch (IOException e) {
172 // NOOP
173 }
174 }
175 }
176 }
177 }
178
179 private void copyFile(InputStream in, OutputStream out) throws IOException {
180 byte[] buffer = new byte[1024];
181 int read;
182 while((read = in.read(buffer)) != -1){
183 out.write(buffer, 0, read);
184 }
185 }
186
187 /**
188 * copy a directory or file to another location
189 *
190 */
191 public void copyTo(CallbackContext callbackContext, JSONObject options) throws JSONException {
192 Log.d(TAG, "copyTo called with " + options.toString());
193 PluginResult result;
194
195 try {
196 JSONObject source = options.getJSONObject("source");
197 String target = options.getString("target");
198
199 if (source.getString("directory").equals("APPLICATION")) {
200 this.copyAssets(source.getString("path"), target);
201 } else {
202 File srcDir = this.getDirectory(source.getString("directory"));
203 File srcFile = new File(srcDir.getPath() + "/" + source.getString("path"));
204
205 if (!srcFile.exists()) {
206 result = new PluginResult(PluginResult.Status.ERROR, "source file or directory does not exist");
207 result.setKeepCallback(false);
208 callbackContext.sendPluginResult(result);
209 return;
210 }
211
212 if (srcFile.isDirectory()) {
213 FileUtils.copyDirectory(srcFile, new File(target));
214 } else {
215 FileUtils.copyFile(srcFile, new File(target));
216 }
217 }
218 } catch (Exception e) {
219 result = new PluginResult(PluginResult.Status.ERROR, e.getMessage());
220 result.setKeepCallback(false);
221 callbackContext.sendPluginResult(result);
222 return;
223 }
224
225 result = new PluginResult(PluginResult.Status.OK);
226 result.setKeepCallback(false);
227 callbackContext.sendPluginResult(result);
228 }
229
230 /**
231 * recursively remove a directory or a file
232 *
233 */
234 public void remove(CallbackContext callbackContext, JSONObject options) throws JSONException {
235 Log.d(TAG, "recursiveRemove called with " + options.toString());
236 String target = options.getString("target");
237 File dest = new File(target);
238 final PluginResult result;
239
240 if (!dest.exists()) {
241 result = new PluginResult(PluginResult.Status.ERROR, "file or directory does not exist");
242 result.setKeepCallback(false);
243 callbackContext.sendPluginResult(result);
244 return;
245 }
246
247 try {
248 FileUtils.forceDelete(dest);
249 } catch (IOException e) {
250 result = new PluginResult(PluginResult.Status.ERROR, e.getMessage());
251 result.setKeepCallback(false);
252 callbackContext.sendPluginResult(result);
253 return;
254 }
255
256 result = new PluginResult(PluginResult.Status.OK);
257 result.setKeepCallback(false);
258 callbackContext.sendPluginResult(result);
259 }
260
261 public void downloadFile(CallbackContext callbackContext, JSONObject options) throws JSONException {
262 Log.d(TAG, "downloadFile called with " + options.toString());
263 String url = options.getString("url");
264 String dest = options.getString("target");
265 final PluginResult result;
266
267 try {
268 URL u = new URL(url);
269 InputStream is = u.openStream();
270
271 DataInputStream dis = new DataInputStream(is);
272
273 byte[] buffer = new byte[1024];
274 int length;
275
276 File downFile = new File(dest);
277 downFile.getParentFile().mkdirs();
278 downFile.createNewFile();
279 FileOutputStream fos = new FileOutputStream(downFile);
280 while ((length = dis.read(buffer))>0) {
281 fos.write(buffer, 0, length);
282 }
283
284 } catch (Exception e) {
285 Log.e(TAG, "downloadFile error", e);
286 result = new PluginResult(PluginResult.Status.ERROR, e.getMessage());
287 result.setKeepCallback(false);
288 callbackContext.sendPluginResult(result);
289 return;
290 }
291 result = new PluginResult(PluginResult.Status.OK);
292 result.setKeepCallback(false);
293 callbackContext.sendPluginResult(result);
294 }
295
296 /**
297 * Get basic app information. Used for the Ionic monitoring service.
298 *
299 * @param callbackContext The callback id used when calling back into JavaScript.
300 */
301 public void getAppInfo(CallbackContext callbackContext) throws JSONException {
302 JSONObject j = new JSONObject();
303
304 try {
305 PackageInfo pInfo = this.cordova.getActivity().getPackageManager().getPackageInfo(this.cordova.getActivity().getPackageName(), 0);
306 String versionName = pInfo.versionName;
307 String name = pInfo.packageName;
308 int versionCode = pInfo.versionCode;
309 String platformVersion = String.valueOf(Build.VERSION.RELEASE);
310
311 j.put("platform", "android");
312 j.put("platformVersion", platformVersion);
313 j.put("version", versionCode);
314 j.put("binaryVersionCode", versionCode);
315 j.put("bundleName", name);
316 j.put("bundleVersion", versionName);
317 j.put("binaryVersionName", versionName);
318 j.put("device", this.uuid);
319 j.put("dataDirectory", toDirUrl(cordova.getActivity().getFilesDir()));
320
321 Log.d(TAG, "Got package info. Version: " + versionName + ", bundleName: " + name + ", versionCode: " + versionCode);
322 final PluginResult result = new PluginResult(PluginResult.Status.OK, j);
323 result.setKeepCallback(false);
324 callbackContext.sendPluginResult(result);
325 } catch(Exception ex) {
326 Log.e(TAG, "Unable to get package info", ex);
327 callbackContext.error(ex.toString());
328 }
329 }
330
331 /**
332 * Grabs a string from the activity's resources.
333 *
334 * @param aString The name of the resource to retrieve
335 * @return The string contents of the resource
336 */
337 private String getStringResourceByName(String aString) {
338 Activity activity = cordova.getActivity();
339 String packageName = activity.getPackageName();
340 int resId = activity.getResources().getIdentifier(aString, "string", packageName);
341 return activity.getString(resId);
342 }
343
344 /**
345 * Get saved prefs configured via code at runtime
346 *
347 */
348 public JSONObject getCustomConfig() throws JSONException {
349 SharedPreferences prefs = this.cordova.getActivity().getApplicationContext().getSharedPreferences("com.ionic.deploy.preferences", Context.MODE_PRIVATE);
350 String prefsString = prefs.getString(this.CUSTOM_PREFS_KEY, null);
351 if (prefsString != null) {
352 JSONObject customPrefs = new JSONObject(prefsString);
353 return customPrefs;
354 }
355 return new JSONObject("{}");
356 }
357
358 /**
359 * Set saved prefs configured via code at runtime
360 *
361 */
362 public void configure(CallbackContext callbackContext, JSONObject newConfig) throws JSONException {
363 Log.i(TAG, "Set custom config called with " + newConfig.toString());
364 SharedPreferences prefs = this.cordova.getActivity().getApplicationContext().getSharedPreferences("com.ionic.deploy.preferences", Context.MODE_PRIVATE);
365 SharedPreferences.Editor editor = prefs.edit();
366 JSONObject storedConfig = this.getCustomConfig();
367 this.mergeObjects(storedConfig, newConfig);
368 editor.putString(this.CUSTOM_PREFS_KEY, storedConfig.toString());
369 editor.commit();
370 Log.i(TAG, "config updated");
371
372 final PluginResult result = new PluginResult(PluginResult.Status.OK, storedConfig);
373 result.setKeepCallback(false);
374 callbackContext.sendPluginResult(result);
375 }
376
377 /**
378 * Get cordova plugin preferences and state information.
379 *
380 * @param callbackContext The callback id used when calling back into JavaScript.
381 */
382 public void getPreferences(CallbackContext callbackContext) throws JSONException {
383
384 JSONObject nativePrefs = this.getNativeConfig();
385 JSONObject customPrefs = this.getCustomConfig();
386
387 // Check for prefs that have been saved before
388 SharedPreferences prefs = this.cordova.getActivity().getApplicationContext().getSharedPreferences("com.ionic.deploy.preferences", Context.MODE_PRIVATE);
389 String prefsString = prefs.getString(this.PREFS_KEY, null);
390 if (prefsString != null) {
391 JSONObject savedPrefs;
392 Log.i(TAG, "Found saved prefs: " + prefsString);
393 // grab the save prefs
394 savedPrefs = new JSONObject(prefsString);
395
396 // update with the lastest things from config.xml
397 this.mergeObjects(savedPrefs, nativePrefs);
398
399 // update with the lastest things from custom configuration
400 this.mergeObjects(savedPrefs, customPrefs);
401
402 final PluginResult result = new PluginResult(PluginResult.Status.OK, savedPrefs);
403 result.setKeepCallback(false);
404 callbackContext.sendPluginResult(result);
405 return;
406 }
407
408 // no saved prefs were found
409 try {
410 nativePrefs.put("updates", new JSONObject("{}"));
411 final PluginResult result = new PluginResult(PluginResult.Status.OK, nativePrefs);
412 result.setKeepCallback(false);
413 callbackContext.sendPluginResult(result);
414 } catch(Exception ex) {
415 Log.e(TAG, "Unable to get preferences", ex);
416 callbackContext.error(ex.toString());
417 }
418 }
419
420 private JSONObject getNativeConfig() throws JSONException {
421 JSONObject j = new JSONObject();
422 int maxV;
423 int minBackgroundDuration;
424 try {
425 maxV = Integer.parseInt(getStringResourceByName("ionic_max_versions"));
426 } catch(NumberFormatException e) {
427 maxV = 2;
428 }
429
430 try {
431 minBackgroundDuration = Integer.parseInt(getStringResourceByName("ionic_min_background_duration"));
432 } catch(NumberFormatException e) {
433 minBackgroundDuration = 30;
434 }
435 String versionName;
436 int versionCode;
437 try {
438 PackageInfo pInfo = this.cordova.getActivity().getPackageManager().getPackageInfo(this.cordova.getActivity().getPackageName(), 0);
439 versionName = pInfo.versionName;
440 versionCode = pInfo.versionCode;
441 } catch(Exception ex) {
442 Log.e(TAG, "Unable to get package info", ex);
443 versionName = "unknown";
444 versionCode = 0;
445 }
446
447 String appId = getStringResourceByName("ionic_app_id");
448 j.put("appId", appId);
449 j.put("disabled", preferences.getBoolean("DisableDeploy", false));
450 j.put("channel", getStringResourceByName("ionic_channel_name"));
451 j.put("host", getStringResourceByName("ionic_update_api"));
452 j.put("updateMethod", getStringResourceByName("ionic_update_method"));
453 j.put("maxVersions", maxV);
454 j.put("minBackgroundDuration", minBackgroundDuration);
455 j.put("binaryVersion", versionName);
456 j.put("binaryVersionName", versionName);
457 j.put("binaryVersionCode", versionCode);
458
459
460 Log.d(TAG, "Got Native Prefs for AppID: " + appId);
461 return j;
462 }
463
464 /**
465 * Add any keys from obj2 into obj1 overwriting them if they exist
466 */
467 private void mergeObjects(JSONObject obj1, JSONObject obj2) {
468 Iterator it = obj2.keys();
469 while (it.hasNext()) {
470 String key = (String)it.next();
471 try {
472 obj1.putOpt(key, obj2.opt(key));
473 } catch (JSONException ex) {
474 Log.d(TAG, "key didn't exist when merging object");
475 }
476 }
477 }
478
479 /**
480 * Set cordova plugin preferences and state information.
481 * @param callbackContext The callback id used when calling back into JavaScript.
482 * @param newPrefs
483 */
484 public void setPreferences(CallbackContext callbackContext, JSONObject newPrefs) {
485 Log.i(TAG, "Set preferences called with prefs" + newPrefs.toString());
486 SharedPreferences prefs = this.cordova.getActivity().getApplicationContext().getSharedPreferences("com.ionic.deploy.preferences", Context.MODE_PRIVATE);
487 SharedPreferences.Editor editor = prefs.edit();
488 editor.putString(this.PREFS_KEY, newPrefs.toString());
489 editor.commit();
490 Log.i(TAG, "preferences updated");
491 final PluginResult result = new PluginResult(PluginResult.Status.OK, newPrefs);
492 result.setKeepCallback(false);
493 callbackContext.sendPluginResult(result);
494 }
495
496 private static String toDirUrl(File f) {
497 return Uri.fromFile(f).toString() + '/';
498 }
499
500}