1 | package com.ionicframework.common;
|
2 |
|
3 | import android.content.Context;
|
4 | import android.content.SharedPreferences;
|
5 | import android.content.res.AssetManager;
|
6 | import android.net.Uri;
|
7 |
|
8 | import org.apache.commons.io.FileUtils;
|
9 | import org.apache.cordova.CordovaWebView;
|
10 | import org.apache.cordova.CallbackContext;
|
11 | import org.apache.cordova.CordovaPlugin;
|
12 | import org.apache.cordova.CordovaInterface;
|
13 | import org.apache.cordova.PluginResult;
|
14 | import org.json.JSONArray;
|
15 | import org.json.JSONObject;
|
16 | import org.json.JSONException;
|
17 |
|
18 | import android.os.Environment;
|
19 | import android.util.Log;
|
20 | import android.app.Activity;
|
21 | import android.content.pm.PackageInfo;
|
22 | import android.os.Build;
|
23 |
|
24 | import java.io.DataInputStream;
|
25 | import java.io.File;
|
26 | import java.io.FileOutputStream;
|
27 | import java.io.IOException;
|
28 | import java.io.InputStream;
|
29 | import java.io.OutputStream;
|
30 | import java.net.URL;
|
31 | import java.util.Iterator;
|
32 | import java.util.UUID;
|
33 |
|
34 | public 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 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 | public void initialize(CordovaInterface cordova, CordovaWebView webView) {
|
56 | super.initialize(cordova, webView);
|
57 |
|
58 |
|
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 |
|
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 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
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 |
|
166 | }
|
167 | }
|
168 | if (out != null) {
|
169 | try {
|
170 | out.close();
|
171 | } catch (IOException e) {
|
172 |
|
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 |
|
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 |
|
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 |
|
298 |
|
299 |
|
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 |
|
333 |
|
334 |
|
335 |
|
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 |
|
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 |
|
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 |
|
379 |
|
380 |
|
381 |
|
382 | public void getPreferences(CallbackContext callbackContext) throws JSONException {
|
383 |
|
384 | JSONObject nativePrefs = this.getNativeConfig();
|
385 | JSONObject customPrefs = this.getCustomConfig();
|
386 |
|
387 |
|
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 |
|
394 | savedPrefs = new JSONObject(prefsString);
|
395 |
|
396 |
|
397 | this.mergeObjects(savedPrefs, nativePrefs);
|
398 |
|
399 |
|
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 |
|
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 |
|
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 |
|
481 |
|
482 |
|
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 | }
|