├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── package.json ├── plugin.xml ├── res ├── img │ ├── Screenshot_2015-10-31-13-42-13.jpg │ └── Screenshot_2015-10-31-13-42-19.jpg ├── layout │ └── appupdate_progress.xml ├── values-bn │ └── appupdate_strings.xml ├── values-de │ └── appupdate_strings.xml ├── values-en │ └── appupdate_strings.xml ├── values-es │ └── appupdate_strings.xml ├── values-fr │ └── appupdate_strings.xml ├── values-it │ └── appupdate_strings.xml ├── values-ko │ └── appupdate_strings.xml ├── values-pl │ └── appupdate_strings.xml ├── values-pt │ └── appupdate_strings.xml ├── values-ru │ └── appupdate_strings.xml ├── values-zh │ └── appupdate_strings.xml ├── values │ └── appupdate_strings.xml └── xml │ ├── appupdate_paths.xml │ └── version.xml ├── src └── android │ ├── AuthenticationOptions.java │ ├── CheckAppUpdate.java │ ├── CheckUpdateThread.java │ ├── Constants.java │ ├── DownloadApkThread.java │ ├── DownloadHandler.java │ ├── GenericFileProvider.java │ ├── GetRemoteXmlThread.java │ ├── LICENSE │ ├── MsgBox.java │ ├── MsgHelper.java │ ├── ParseXmlService.java │ ├── UIValues.java │ ├── UpdateManager.java │ ├── Utils.java │ └── Version.java ├── syncFromDemo.sh └── www └── AppUpdate.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | npm-debug.log 3 | *.iml 4 | 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "4.2" 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Wen Luo 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ![travis](https://travis-ci.org/vaenow/cordova-plugin-app-update.svg?branch=master) 4 | 5 | [![NPM](https://nodei.co/npm/cordova-plugin-app-update.png?downloads=true&downloadRank=true)](https://nodei.co/npm/cordova-plugin-app-update/) 6 | 7 | # cordova-plugin-app-update 8 | App updater for Cordova/PhoneGap 9 | 10 | # Demo 11 | Try it yourself: 12 | 13 | Just clone and install this demo. 14 | [cordova-plugin-app-update-DEMO](https://github.com/vaenow/cordova-plugin-app-update-demo) 15 | :tada: 16 | 17 | * 如果喜欢它,请别忘了给我一颗鼓励的星 18 | * Support me a `Star` if it is necessary. :+1: 19 | 20 | # Preview 21 | ![enter image description here](https://raw.githubusercontent.com/vaenow/cordova-plugin-app-update/master/res/img/Screenshot_2015-10-31-13-42-13.jpg) 22 | 23 | # 24 | 25 | ![enter image description here](https://raw.githubusercontent.com/vaenow/cordova-plugin-app-update/master/res/img/Screenshot_2015-10-31-13-42-19.jpg) 26 | 27 | # Install 28 | 29 | ### Latest published version on npm (with Cordova CLI >= 5.0.0) 30 | 31 | > `"cordova-android": "6.3.0"` 32 | 33 | `cordova plugin add cordova-plugin-app-update --save` 34 | 35 | # Usage 36 | 37 | - Simple: 38 | ```js 39 | var updateUrl = "http://192.168.0.1/version.xml"; 40 | window.AppUpdate.checkAppUpdate(onSuccess, onFail, updateUrl); 41 | ``` 42 | 43 | - Verbose 44 | ```js 45 | var appUpdate = cordova.require('cordova-plugin-app-update.AppUpdate'); 46 | var updateUrl = "http://192.168.0.1/version.xml"; 47 | appUpdate.checkAppUpdate(onSuccess, onFail, updateUrl); 48 | ``` 49 | 50 | - Auth download [MORE](https://github.com/vaenow/cordova-plugin-app-update/pull/62) 51 | ```js 52 | appUpdate.checkAppUpdate(onSuccess, onFail, updateUrl, { 53 | 'authType' : 'basic', 54 | 'username' : 'test', 55 | 'password' : 'test' 56 | }) 57 | ``` 58 | 59 | - Skip dialog boxes 60 | ```js 61 | appUpdate.checkAppUpdate(onSuccess, onFail, updateUrl, { 62 | 'skipPromptDialog' : true, 63 | 'skipProgressDialog' : true 64 | }) 65 | ``` 66 | 67 | ### versionCode 68 | 69 | You can simply get the versionCode from typing those code in `Console` 70 | 71 | ```js 72 | var versionCode = AppVersion.build 73 | console.log(versionCode) // 302048 74 | ``` 75 | 76 | 77 | versionName | versionCode 78 | ------- | ---------------- 79 | 0.0.1 | 18 80 | 0.3.4 | 3048 81 | 3.2.4 | 302048 82 | 12.234.221 | 1436218 83 | 84 | ### server version.xml file 85 | 86 | ```xml 87 | 88 | 302048 89 | name 90 | http://192.168.0.1/android.apk 91 | 92 | ``` 93 | 94 | ### `checkAppUpdate` code 95 | 96 | ```java 97 | /** 98 | * 对比版本号 99 | */ 100 | int VERSION_NEED_UPDATE = 201; //检查到需要更新; need update 101 | int VERSION_UP_TO_UPDATE = 202; //软件是不需要更新;version up to date 102 | int VERSION_UPDATING = 203; //软件正在更新;version is updating 103 | 104 | /** 105 | * 版本解析错误 106 | */ 107 | int VERSION_RESOLVE_FAIL = 301; //版本文件解析错误 version-xml file resolve fail 108 | int VERSION_COMPARE_FAIL = 302; //版本文件对比错误 version-xml file compare fail 109 | 110 | /** 111 | * 网络错误 112 | */ 113 | int REMOTE_FILE_NOT_FOUND = 404; 114 | int NETWORK_ERROR = 405; 115 | 116 | /** 117 | * 没有相应的方法 118 | */ 119 | int NO_SUCH_METHOD = 501; 120 | 121 | /** 122 | * Permissions 123 | */ 124 | int PERMISSION_DENIED = 601; 125 | 126 | /** 127 | * 未知错误 128 | */ 129 | int UNKNOWN_ERROR = 901; 130 | ``` 131 | # Languages 132 | * 🇨🇳 zh 133 | * 🇺🇸 en 134 | * 🇩🇪 de 135 | * 🇫🇷 fr 136 | * 🇵🇹 pt 137 | * 🇧🇩 bn 138 | * 🇵🇱 pl 139 | * 🇮🇹 it 140 | * 🇪🇸 es 141 | * 🇷🇺 ru 142 | * 🇰🇷 ko 143 | 144 | # Platforms 145 | Android only 146 | 147 | # License 148 | MIT 149 | 150 | # :snowflake: :beers: 151 | 152 | * Please let me know if you have any questions. 153 | 154 | 155 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-app-update", 3 | "version": "2.0.2", 4 | "description": "Cordova App Update", 5 | "cordova": { 6 | "id": "cordova-plugin-app-update", 7 | "platforms": [ 8 | "android" 9 | ] 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/vaenow/cordova-plugin-app-update.git" 14 | }, 15 | "keywords": [ 16 | "cordova", 17 | "app", 18 | "auto", 19 | "update", 20 | "updater", 21 | "dynamic", 22 | "ecosystem:cordova", 23 | "cordova-android" 24 | ], 25 | "author": "vaenow", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/vaenow/cordova-plugin-app-update/issues" 29 | }, 30 | "scripts": { 31 | "test": "npm run jshint", 32 | "jshint": "node node_modules/jshint/bin/jshint www && node node_modules/jshint/bin/jshint src && node node_modules/jshint/bin/jshint tests" 33 | }, 34 | "homepage": "https://github.com/vaenow/cordova-plugin-app-update#readme", 35 | "devDependencies": { 36 | "jshint": "^2.6.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | AppUpdate 5 | Cordova App Update 6 | Apache 2.0 7 | cordova,update,app,auto,updater 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 34 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /res/img/Screenshot_2015-10-31-13-42-13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaenow/cordova-plugin-app-update/2fe6973a597f0fda10c40401f133e4bbc4905b60/res/img/Screenshot_2015-10-31-13-42-13.jpg -------------------------------------------------------------------------------- /res/img/Screenshot_2015-10-31-13-42-19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaenow/cordova-plugin-app-update/2fe6973a597f0fda10c40401f133e4bbc4905b60/res/img/Screenshot_2015-10-31-13-42-19.jpg -------------------------------------------------------------------------------- /res/layout/appupdate_progress.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /res/values-bn/appupdate_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | এখন অ্যাপের সর্বশেষ সংস্করণ আছে 4 | নতুন সংস্করণ 5 | অ্যাপের নতুন সংস্করণ দরকার 6 | আপডেট করুন 7 | আপডেট করা হচ্ছে 8 | দরকার নাই 9 | অ্যাপ ব্যাবহার করার মাঝে ডাউনলোড করুন 10 | ডাউনলোড পূর্ণ 11 | ডাউনলোড পূর্ণ 12 | আবার ডাউনলোড করুন 13 | নিজে ইন্সটল করুন 14 | 15 | সমস্যা 16 | এই মুহূর্তে Data চালু নাই অথবা নেটওয়ার্ক পাওয়া জাচ্ছে না 17 | ঠিকাছে 18 | 19 | -------------------------------------------------------------------------------- /res/values-de/appupdate_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Aktuellste Version installiert 4 | Update verfügbar 5 | Die Software muss aktualisiert werden! 6 | Update 7 | Wird aktualisiert 8 | Abbrechen 9 | Im Hintergrund aktualisieren 10 | Download abgeschlossen 11 | Download wiederholen 12 | Manuell installieren 13 | 14 | Fehler 15 | Keine Internetverbindung. Bitte prüfen Sie Ihre Netzwerkeinstellungen. 16 | OK 17 | 18 | -------------------------------------------------------------------------------- /res/values-en/appupdate_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Now you have the latest update 4 | New update 5 | The software need to be updated! 6 | Update 7 | Updating 8 | Cancel 9 | Update in background 10 | Download complete 11 | Download complete 12 | Download again 13 | Install manually 14 | 15 | Error 16 | The current network is unavailable , please check your network settings. 17 | OK 18 | 19 | -------------------------------------------------------------------------------- /res/values-es/appupdate_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Ahora tienes la última versión 4 | Nueva actualización 5 | El software necesita ser actualizado 6 | Actualizar 7 | Actualizando 8 | Cancelar 9 | Esconder progreso 10 | Descarga completa 11 | Descargar nuevamente 12 | Instalar manualmente 13 | 14 | Error 15 | La red actual no está disponible, por favor chequee su configuración de red. 16 | OK 17 | 18 | -------------------------------------------------------------------------------- /res/values-fr/appupdate_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Vous possédez la dernière version disponible! 4 | Nouvelle Mise à Jour 5 | Vous devez mettre l\'application à jour! 6 | Mise à Jour 7 | Mise à jour en cours 8 | Annuler 9 | Mettre à jour en arrière-plan... 10 | Mise à jour terminée 11 | Mise à jour terminée 12 | Re-télécharger 13 | Installer manuellement 14 | 15 | Erreur 16 | Connexion internet indisponible, merci de vérifier votre connexion. 17 | OK 18 | 19 | -------------------------------------------------------------------------------- /res/values-it/appupdate_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Sei in possesso dell\' ultima versione 4 | Nuovo aggiornamento 5 | E\' disponibile una nuova versione! 6 | Aggiorna 7 | Aggiornamento 8 | Annulla 9 | Aggiorna in background 10 | Download completato con successo 11 | Download completato 12 | Scarica di nuovo 13 | Installa manualmente 14 | 15 | Errore 16 | Si è verificato un errore di rete, controlla la tua connessione 17 | OK 18 | 19 | -------------------------------------------------------------------------------- /res/values-ko/appupdate_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 최신 버전입니다 4 | 새로운 업데이트 5 | 업데이트가 필요합니다! 6 | 업데이트 7 | 업데이트 중입니다 8 | 취소 9 | 백그라운드 업데이트 10 | 다운로드 완료 11 | 다운로드 완료 12 | 다시 다운로드 13 | 수동으로 다운로드 14 | 15 | 에러 16 | 네트워크를 사용할 수 없습니다, 네트워크 상태를 확인해주세요. 17 | 확인 18 | 19 | -------------------------------------------------------------------------------- /res/values-pl/appupdate_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Posiadasz najnowszą wersję 4 | Nowa aktualizacja 5 | Oprogramowanie musi zostać zaktualizowane! 6 | Aktualizuj 7 | Aktualizowanie 8 | Anuluj 9 | Aktualizuj w tle 10 | Pobieranie zakończone 11 | Pobieranie 12 | Pobierz ponownie 13 | Instaluj ręcznie 14 | 15 | Błąd 16 | Obecnie sieć jest niedostępna, sprawdź proszę ustawienia sieciowe. 17 | OK 18 | 19 | -------------------------------------------------------------------------------- /res/values-pt/appupdate_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Agora você tem a última versão 4 | Nova atualização 5 | O aplicativo precisa ser atualizado ! 6 | Atualizar 7 | Atualizando 8 | Cancelar 9 | Atualizar em segundo plano 10 | Download completo 11 | Download completo 12 | Baixar novamente 13 | Instalar manualmente 14 | 15 | Erro 16 | A rede não está disponível, por favor verifique suas configurações de conexão. 17 | OK 18 | 19 | -------------------------------------------------------------------------------- /res/values-ru/appupdate_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Установлена последняя версия 4 | Новое обновление 5 | Требуется установить обновление! 6 | Обновить 7 | Обновляется 8 | Отмена 9 | Обновить в фоне 10 | Скачивание завершено 11 | Скачивание завершено 12 | Скачать заново 13 | Установить вручную 14 | 15 | Ошибка 16 | Ошибка сети, проверьте настройки сети. 17 | Ок 18 | 19 | -------------------------------------------------------------------------------- /res/values-zh/appupdate_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 已经是最新版本 4 | 软件更新 5 | 软件框架有更新啦! 6 | 更新 7 | 正在更新 8 | 取消 9 | 转到后台更新 10 | 下载完毕 11 | 重新下载 12 | 手动安装 13 | 14 | 错误 15 | 当前网络不可用,请检查你的网络设置。 16 | 知道了 17 | 18 | -------------------------------------------------------------------------------- /res/values/appupdate_strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Now you have the latest update 4 | New update 5 | The software need to be updated! 6 | Update 7 | Updating 8 | Cancel 9 | Update in background 10 | Download complete 11 | Download complete 12 | Download again 13 | Install manually 14 | 15 | Error 16 | The current network is unavailable , please check your network settings. 17 | OK 18 | 19 | -------------------------------------------------------------------------------- /res/xml/appupdate_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /res/xml/version.xml: -------------------------------------------------------------------------------- 1 | 2 | 2222 3 | name 4 | http://192.168.3.102/android.apk 5 | -------------------------------------------------------------------------------- /src/android/AuthenticationOptions.java: -------------------------------------------------------------------------------- 1 | package android; 2 | 3 | 4 | import android.util.Base64; 5 | 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | 9 | import java.nio.charset.StandardCharsets; 10 | 11 | public class AuthenticationOptions { 12 | 13 | private String authType; 14 | private String username; 15 | private String password; 16 | 17 | public AuthenticationOptions(JSONObject options) { 18 | try { 19 | this.setAuthType(options.getString("authType")); 20 | this.setUsername(options.getString("username")); 21 | this.setPassword(options.getString("password")); 22 | } catch (JSONException e){ 23 | // If there is any error then ensure that auth type is unset 24 | this.setAuthType(""); 25 | } 26 | } 27 | 28 | /** 29 | * Flag indicating authentication credentials have been set 30 | * 31 | * @return boolean flag indicating if there are authentication credentials 32 | */ 33 | public boolean hasCredentials() { 34 | return authType.equals("basic"); 35 | } 36 | 37 | public String getEncodedAuthorization() { 38 | return "Basic " + Base64.encodeToString((this.username + ":" + this.password) 39 | .getBytes(StandardCharsets.UTF_8), Base64.DEFAULT); 40 | } 41 | 42 | public String getAuthType() { 43 | return authType; 44 | } 45 | 46 | public void setAuthType(String authType) { 47 | this.authType = authType; 48 | } 49 | 50 | public String getUsername() { 51 | return username; 52 | } 53 | 54 | public void setUsername(String username) { 55 | this.username = username; 56 | } 57 | 58 | public String getPassword() { 59 | return password; 60 | } 61 | 62 | public void setPassword(String password) { 63 | this.password = password; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/android/CheckAppUpdate.java: -------------------------------------------------------------------------------- 1 | package com.vaenow.appupdate.android; 2 | 3 | import android.app.Activity; 4 | import android.Manifest; 5 | import android.os.Build; 6 | import android.net.Uri; 7 | import android.provider.Settings; 8 | import android.content.Intent; 9 | import android.content.pm.PackageManager; 10 | 11 | import org.apache.cordova.CallbackContext; 12 | import org.apache.cordova.CordovaPlugin; 13 | import org.apache.cordova.BuildHelper; 14 | 15 | import org.json.JSONArray; 16 | import org.json.JSONException; 17 | 18 | /** 19 | * Created by LuoWen on 2015/10/27. 20 | */ 21 | public class CheckAppUpdate extends CordovaPlugin { 22 | public static final String TAG = "CheckAppUpdate"; 23 | 24 | @Override 25 | public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { 26 | if (action.equals("checkAppUpdate")) { 27 | getUpdateManager().options(args, callbackContext); 28 | if (verifyInstallPermission() && verifyOtherPermissions()) 29 | getUpdateManager().checkUpdate(); 30 | return true; 31 | } 32 | 33 | callbackContext.error(Utils.makeJSON(Constants.NO_SUCH_METHOD, "No such method: " + action)); 34 | return false; 35 | } 36 | 37 | ////////// 38 | // Update Manager 39 | ////////// 40 | 41 | // UpdateManager singleton 42 | private UpdateManager updateManager = null; 43 | 44 | // Generate or retrieve the UpdateManager singleton 45 | public UpdateManager getUpdateManager() { 46 | if (updateManager == null) 47 | updateManager = new UpdateManager(cordova.getActivity(), cordova); 48 | 49 | return updateManager; 50 | } 51 | 52 | ////////// 53 | // Permissions 54 | ////////// 55 | 56 | private static final int INSTALL_PERMISSION_REQUEST_CODE = 0; 57 | private static final int UNKNOWN_SOURCES_PERMISSION_REQUEST_CODE = 1; 58 | private static final int OTHER_PERMISSIONS_REQUEST_CODE = 2; 59 | 60 | // Other necessary permissions for this plugin. 61 | private static String[] OTHER_PERMISSIONS = { 62 | Manifest.permission.INTERNET, 63 | Manifest.permission.READ_EXTERNAL_STORAGE, 64 | Manifest.permission.WRITE_EXTERNAL_STORAGE 65 | }; 66 | 67 | // Prompt user for install permission if we don't already have it. 68 | public boolean verifyInstallPermission() { 69 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 70 | if (!cordova.getActivity().getPackageManager().canRequestPackageInstalls()) { 71 | String applicationId = (String) BuildHelper.getBuildConfigValue(cordova.getActivity(), "APPLICATION_ID"); 72 | Uri packageUri = Uri.parse("package:" + applicationId); 73 | Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES) 74 | .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) 75 | .setData(packageUri); 76 | cordova.setActivityResultCallback(this); 77 | cordova.getActivity().startActivityForResult(intent, INSTALL_PERMISSION_REQUEST_CODE); 78 | return false; 79 | } 80 | } 81 | else { 82 | try { 83 | if (Settings.Secure.getInt(cordova.getActivity().getContentResolver(), Settings.Secure.INSTALL_NON_MARKET_APPS) != 1) { 84 | Intent intent = new Intent(Settings.ACTION_SECURITY_SETTINGS); 85 | cordova.setActivityResultCallback(this); 86 | cordova.getActivity().startActivityForResult(intent, UNKNOWN_SOURCES_PERMISSION_REQUEST_CODE); 87 | return false; 88 | } 89 | } 90 | catch (Settings.SettingNotFoundException e) {} 91 | } 92 | 93 | return true; 94 | } 95 | 96 | // Prompt user for all other permissions if we don't already have them all. 97 | public boolean verifyOtherPermissions() { 98 | boolean hasOtherPermissions = true; 99 | for (String permission:OTHER_PERMISSIONS) 100 | hasOtherPermissions = hasOtherPermissions && cordova.hasPermission(permission); 101 | 102 | if (!hasOtherPermissions) { 103 | cordova.requestPermissions(this, OTHER_PERMISSIONS_REQUEST_CODE, OTHER_PERMISSIONS); 104 | return false; 105 | } 106 | 107 | return true; 108 | } 109 | 110 | // React to user's response to our request for install permission. 111 | @Override 112 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 113 | if (requestCode == INSTALL_PERMISSION_REQUEST_CODE) { 114 | if (!cordova.getActivity().getPackageManager().canRequestPackageInstalls()) { 115 | getUpdateManager().permissionDenied("Permission Denied: " + Manifest.permission.REQUEST_INSTALL_PACKAGES); 116 | return; 117 | } 118 | 119 | if (verifyOtherPermissions()) 120 | getUpdateManager().checkUpdate(); 121 | } 122 | else if (requestCode == UNKNOWN_SOURCES_PERMISSION_REQUEST_CODE) { 123 | try { 124 | if (Settings.Secure.getInt(cordova.getActivity().getContentResolver(), Settings.Secure.INSTALL_NON_MARKET_APPS) != 1) { 125 | getUpdateManager().permissionDenied("Permission Denied: " + Settings.Secure.INSTALL_NON_MARKET_APPS); 126 | return; 127 | } 128 | } 129 | catch (Settings.SettingNotFoundException e) {} 130 | 131 | if (verifyOtherPermissions()) 132 | getUpdateManager().checkUpdate(); 133 | } 134 | } 135 | 136 | // React to user's response to our request for other permissions. 137 | @Override 138 | public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) { 139 | if (requestCode == OTHER_PERMISSIONS_REQUEST_CODE) { 140 | for (int i = 0; i < permissions.length; i++) { 141 | if (grantResults[i] == PackageManager.PERMISSION_DENIED) { 142 | getUpdateManager().permissionDenied("Permission Denied: " + permissions[i]); 143 | return; 144 | } 145 | } 146 | 147 | getUpdateManager().checkUpdate(); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/android/CheckUpdateThread.java: -------------------------------------------------------------------------------- 1 | package com.vaenow.appupdate.android; 2 | 3 | import android.AuthenticationOptions; 4 | import android.content.Context; 5 | import android.content.pm.PackageManager.NameNotFoundException; 6 | import android.os.Handler; 7 | import android.util.Base64; 8 | import org.apache.cordova.LOG; 9 | import org.json.JSONObject; 10 | import org.json.JSONException; 11 | 12 | import java.io.FileNotFoundException; 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.net.HttpURLConnection; 16 | import java.net.URL; 17 | import java.util.HashMap; 18 | import java.util.List; 19 | 20 | import java.nio.charset.StandardCharsets; 21 | 22 | /** 23 | * Created by LuoWen on 2015/12/14. 24 | */ 25 | public class CheckUpdateThread implements Runnable { 26 | private String TAG = "CheckUpdateThread"; 27 | 28 | /* 保存解析的XML信息 */ 29 | HashMap mHashMap; 30 | private Context mContext; 31 | private List queue; 32 | private String packageName; 33 | private String updateXmlUrl; 34 | private AuthenticationOptions authentication; 35 | private Handler mHandler; 36 | 37 | private void setMHashMap(HashMap mHashMap) { 38 | this.mHashMap = mHashMap; 39 | } 40 | 41 | public HashMap getMHashMap() { 42 | return mHashMap; 43 | } 44 | 45 | public CheckUpdateThread(Context mContext, Handler mHandler, List queue, String packageName, String updateXmlUrl, JSONObject options) { 46 | this.mContext = mContext; 47 | this.queue = queue; 48 | this.packageName = packageName; 49 | this.updateXmlUrl = updateXmlUrl; 50 | this.authentication = new AuthenticationOptions(options); 51 | this.mHandler = mHandler; 52 | } 53 | 54 | @Override 55 | public void run() { 56 | int versionCodeLocal = getVersionCodeLocal(mContext); // 获取当前软件版本 57 | int versionCodeRemote = getVersionCodeRemote(); //获取服务器当前软件版本 58 | 59 | queue.clear(); //ensure the queue is empty 60 | queue.add(new Version(versionCodeLocal, versionCodeRemote)); 61 | 62 | if (versionCodeLocal == 0 || versionCodeRemote == 0) { 63 | mHandler.sendEmptyMessage(Constants.VERSION_RESOLVE_FAIL); 64 | } else { 65 | mHandler.sendEmptyMessage(Constants.VERSION_COMPARE_START); 66 | } 67 | } 68 | 69 | /** 70 | * 通过url返回文件 71 | * 72 | * @param path 73 | * @return 74 | */ 75 | private InputStream returnFileIS(String path) { 76 | LOG.d(TAG, "returnFileIS.."); 77 | 78 | URL url = null; 79 | InputStream is = null; 80 | 81 | try { 82 | url = new URL(path); 83 | HttpURLConnection conn = (HttpURLConnection) url.openConnection();//利用HttpURLConnection对象,我们可以从网络中获取网页数据. 84 | 85 | if(this.authentication.hasCredentials()){ 86 | conn.setRequestProperty("Authorization", this.authentication.getEncodedAuthorization()); 87 | } 88 | 89 | conn.setDoInput(true); 90 | conn.connect(); 91 | is = conn.getInputStream(); //得到网络返回的输入流 92 | } catch (FileNotFoundException e) { 93 | e.printStackTrace(); 94 | mHandler.sendEmptyMessage(Constants.REMOTE_FILE_NOT_FOUND); 95 | } catch (IOException e) { 96 | e.printStackTrace(); 97 | mHandler.sendEmptyMessage(Constants.NETWORK_ERROR); 98 | } 99 | 100 | return is; 101 | } 102 | 103 | /** 104 | * 获取软件版本号 105 | *

106 | * It's weird, I don't know why. 107 | *

108 |      * versionName -> versionCode
109 |      * 0.0.1    ->  12
110 |      * 0.3.4    ->  3042
111 |      * 3.2.4    ->  302042
112 |      * 12.234.221 -> 1436212
113 |      * 
114 | * 115 | * @param context 116 | * @return 117 | */ 118 | private int getVersionCodeLocal(Context context) { 119 | LOG.d(TAG, "getVersionCode.."); 120 | 121 | int versionCode = 0; 122 | try { 123 | // 获取软件版本号,对应AndroidManifest.xml下android:versionCode 124 | versionCode = context.getPackageManager().getPackageInfo(packageName, 0).versionCode; 125 | } catch (NameNotFoundException e) { 126 | e.printStackTrace(); 127 | } 128 | return versionCode; 129 | } 130 | 131 | /** 132 | * 获取服务器软件版本号 133 | * 134 | * @return 135 | */ 136 | private int getVersionCodeRemote() { 137 | int versionCodeRemote = 0; 138 | 139 | InputStream is = returnFileIS(updateXmlUrl); 140 | // 解析XML文件。 由于XML文件比较小,因此使用DOM方式进行解析 141 | ParseXmlService service = new ParseXmlService(); 142 | try { 143 | setMHashMap(service.parseXml(is)); 144 | } catch (Exception e) { 145 | e.printStackTrace(); 146 | } 147 | if (null != getMHashMap()) { 148 | versionCodeRemote = Integer.valueOf(getMHashMap().get("version")); 149 | } 150 | 151 | return versionCodeRemote; 152 | } 153 | } -------------------------------------------------------------------------------- /src/android/Constants.java: -------------------------------------------------------------------------------- 1 | package com.vaenow.appupdate.android; 2 | 3 | /** 4 | * Created by LuoWen on 2015/12/14. 5 | */ 6 | public interface Constants { 7 | /* 下载中 */ 8 | int DOWNLOAD = 1; 9 | /* 下载结束 */ 10 | int DOWNLOAD_FINISH = 2; 11 | /* 点击开始下载按钮*/ 12 | int DOWNLOAD_CLICK_START = 3; 13 | 14 | /** 15 | * 对比版本号 16 | */ 17 | int VERSION_COMPARE_START = 200; //private 开始对比版本号; start to compare version 18 | int VERSION_NEED_UPDATE = 201; //检查到需要更新; need update 19 | int VERSION_UP_TO_UPDATE = 202; //软件是不需要更新;version up to date 20 | int VERSION_UPDATING = 203; //软件正在更新;version is updating 21 | 22 | /** 23 | * 版本解析错误 24 | */ 25 | int VERSION_RESOLVE_FAIL = 301; //版本文件解析错误 version-xml file resolve fail 26 | int VERSION_COMPARE_FAIL = 302; //版本文件对比错误 version-xml file compare fail 27 | 28 | /** 29 | * 网络错误 30 | */ 31 | int REMOTE_FILE_NOT_FOUND = 404; 32 | int NETWORK_ERROR = 405; 33 | 34 | /** 35 | * 没有相应的方法 36 | */ 37 | int NO_SUCH_METHOD = 501; 38 | 39 | /** 40 | * Permissions 41 | */ 42 | int PERMISSION_DENIED = 601; 43 | 44 | /** 45 | * 未知错误 46 | */ 47 | int UNKNOWN_ERROR = 901; 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/android/DownloadApkThread.java: -------------------------------------------------------------------------------- 1 | package com.vaenow.appupdate.android; 2 | 3 | import android.AuthenticationOptions; 4 | import android.app.AlertDialog; 5 | import android.content.Context; 6 | import android.os.Environment; 7 | import android.os.Handler; 8 | import android.widget.ProgressBar; 9 | import android.util.Base64; 10 | import org.json.JSONObject; 11 | import org.json.JSONException; 12 | 13 | import java.io.File; 14 | import java.io.FileOutputStream; 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.net.HttpURLConnection; 18 | import java.net.MalformedURLException; 19 | import java.net.URL; 20 | import java.util.HashMap; 21 | 22 | import java.nio.charset.StandardCharsets; 23 | 24 | /** 25 | * 下载文件线程 26 | */ 27 | public class DownloadApkThread implements Runnable { 28 | private String TAG = "DownloadApkThread"; 29 | 30 | /* 保存解析的XML信息 */ 31 | HashMap mHashMap; 32 | /* 下载保存路径 */ 33 | private String mSavePath; 34 | /* 记录进度条数量 */ 35 | private int progress; 36 | /* 是否取消更新 */ 37 | private boolean cancelUpdate = false; 38 | private AlertDialog mDownloadDialog; 39 | private DownloadHandler downloadHandler; 40 | private Handler mHandler; 41 | private AuthenticationOptions authentication; 42 | 43 | public DownloadApkThread(Context mContext, Handler mHandler, ProgressBar mProgress, AlertDialog mDownloadDialog, HashMap mHashMap, JSONObject options) { 44 | this.mDownloadDialog = mDownloadDialog; 45 | this.mHashMap = mHashMap; 46 | this.mHandler = mHandler; 47 | this.authentication = new AuthenticationOptions(options); 48 | 49 | this.mSavePath = Environment.getExternalStorageDirectory() + "/" + "download"; // SD Path 50 | this.downloadHandler = new DownloadHandler(mContext, mProgress, mDownloadDialog, this.mSavePath, mHashMap); 51 | } 52 | 53 | 54 | @Override 55 | public void run() { 56 | downloadAndInstall(); 57 | // 取消下载对话框显示 58 | // mDownloadDialog.dismiss(); 59 | } 60 | 61 | public void cancelBuildUpdate() { 62 | this.cancelUpdate = true; 63 | } 64 | 65 | private void downloadAndInstall() { 66 | try { 67 | // 判断SD卡是否存在,并且是否具有读写权限 68 | if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 69 | // 获得存储卡的路径 70 | URL url = new URL(mHashMap.get("url")); 71 | // 创建连接 72 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 73 | 74 | if(this.authentication.hasCredentials()){ 75 | conn.setRequestProperty("Authorization", this.authentication.getEncodedAuthorization()); 76 | } 77 | 78 | conn.connect(); 79 | // 获取文件大小 80 | int length = conn.getContentLength(); 81 | // 创建输入流 82 | InputStream is = conn.getInputStream(); 83 | 84 | File file = new File(mSavePath); 85 | // 判断文件目录是否存在 86 | if (!file.exists()) { 87 | file.mkdir(); 88 | } 89 | File apkFile = new File(mSavePath, mHashMap.get("name")+".apk"); 90 | FileOutputStream fos = new FileOutputStream(apkFile); 91 | int count = 0; 92 | // 缓存 93 | byte buf[] = new byte[1024]; 94 | 95 | // 写入到文件中 96 | do { 97 | int numread = is.read(buf); 98 | count += numread; 99 | // 计算进度条位置 100 | progress = (int) (((float) count / length) * 100); 101 | downloadHandler.updateProgress(progress); 102 | // 更新进度 103 | downloadHandler.sendEmptyMessage(Constants.DOWNLOAD); 104 | if (numread <= 0) { 105 | // 下载完成 106 | downloadHandler.sendEmptyMessage(Constants.DOWNLOAD_FINISH); 107 | mHandler.sendEmptyMessage(Constants.DOWNLOAD_FINISH); 108 | break; 109 | } 110 | // 写入文件 111 | fos.write(buf, 0, numread); 112 | } while (!cancelUpdate);// 点击取消就停止下载. 113 | fos.close(); 114 | is.close(); 115 | } 116 | } catch (MalformedURLException e) { 117 | e.printStackTrace(); 118 | } catch (IOException e) { 119 | e.printStackTrace(); 120 | } 121 | 122 | } 123 | } -------------------------------------------------------------------------------- /src/android/DownloadHandler.java: -------------------------------------------------------------------------------- 1 | package com.vaenow.appupdate.android; 2 | 3 | import org.apache.cordova.BuildHelper; 4 | 5 | import android.app.AlertDialog; 6 | import android.app.Activity; 7 | import android.content.Context; 8 | import android.content.DialogInterface; 9 | import android.content.Intent; 10 | import android.net.Uri; 11 | import android.os.Build; 12 | import android.os.Handler; 13 | import android.os.Message; 14 | import android.view.View; 15 | import android.view.View.OnClickListener; 16 | import android.widget.ProgressBar; 17 | import android.support.v4.content.FileProvider; 18 | import java.io.File; 19 | import java.util.HashMap; 20 | 21 | import org.apache.cordova.LOG; 22 | 23 | /** 24 | * Created by LuoWen on 2015/12/14. 25 | */ 26 | public class DownloadHandler extends Handler { 27 | private String TAG = "DownloadHandler"; 28 | 29 | private Context mContext; 30 | /* 更新进度条 */ 31 | private ProgressBar mProgress; 32 | /* 记录进度条数量 */ 33 | private int progress; 34 | /* 下载保存路径 */ 35 | private String mSavePath; 36 | /* 保存解析的XML信息 */ 37 | private HashMap mHashMap; 38 | private MsgHelper msgHelper; 39 | private AlertDialog mDownloadDialog; 40 | 41 | public DownloadHandler(Context mContext, ProgressBar mProgress, AlertDialog mDownloadDialog, String mSavePath, HashMap mHashMap) { 42 | this.msgHelper = new MsgHelper(mContext.getPackageName(), mContext.getResources()); 43 | this.mDownloadDialog = mDownloadDialog; 44 | this.mContext = mContext; 45 | this.mProgress = mProgress; 46 | this.mSavePath = mSavePath; 47 | this.mHashMap = mHashMap; 48 | } 49 | 50 | public void handleMessage(Message msg) { 51 | switch (msg.what) { 52 | // 正在下载 53 | case Constants.DOWNLOAD: 54 | // 设置进度条位置 55 | mProgress.setProgress(progress); 56 | break; 57 | case Constants.DOWNLOAD_FINISH: 58 | updateMsgDialog(); 59 | // 安装文件 60 | installApk(); 61 | break; 62 | default: 63 | break; 64 | } 65 | } 66 | 67 | public void updateProgress(int progress) { 68 | this.progress = progress; 69 | } 70 | 71 | public void updateMsgDialog() { 72 | mDownloadDialog.setTitle(msgHelper.getString(MsgHelper.DOWNLOAD_COMPLETE_TITLE)); 73 | if (mDownloadDialog.isShowing()) { 74 | mDownloadDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE); //Update in background 75 | mDownloadDialog.getButton(DialogInterface.BUTTON_NEUTRAL).setVisibility(View.VISIBLE); //Install Manually 76 | mDownloadDialog.getButton(DialogInterface.BUTTON_POSITIVE).setVisibility(View.VISIBLE); //Download Again 77 | 78 | mDownloadDialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener(downloadCompleteOnClick); 79 | } 80 | } 81 | 82 | private OnClickListener downloadCompleteOnClick = new OnClickListener() { 83 | @Override 84 | public void onClick(View view) { 85 | installApk(); 86 | } 87 | }; 88 | 89 | /** 90 | * 安装APK文件 91 | */ 92 | private void installApk() { 93 | LOG.d(TAG, "Installing APK"); 94 | 95 | File apkFile = new File(mSavePath, mHashMap.get("name")+".apk"); 96 | if (!apkFile.exists()) { 97 | LOG.e(TAG, "Could not find APK: " + mHashMap.get("name")); 98 | return; 99 | } 100 | 101 | LOG.d(TAG, "APK Filename: " + apkFile.toString()); 102 | 103 | // 通过Intent安装APK文件 104 | if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){ 105 | LOG.d(TAG, "Build SDK Greater than or equal to Nougat"); 106 | String applicationId = (String) BuildHelper.getBuildConfigValue((Activity) mContext, "APPLICATION_ID"); 107 | Uri apkUri = FileProvider.getUriForFile(mContext, applicationId + ".appupdate.provider", apkFile); 108 | Intent i = new Intent(Intent.ACTION_INSTALL_PACKAGE); 109 | i.setData(apkUri); 110 | i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 111 | mContext.startActivity(i); 112 | }else{ 113 | LOG.d(TAG, "Build SDK less than Nougat"); 114 | Intent i = new Intent(Intent.ACTION_VIEW); 115 | i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 116 | i.setDataAndType(Uri.parse("file://" + apkFile.toString()), "application/vnd.android.package-archive"); 117 | mContext.startActivity(i); 118 | } 119 | 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/android/GenericFileProvider.java: -------------------------------------------------------------------------------- 1 | package com.vaenow.appupdate.android; 2 | 3 | import android.support.v4.content.FileProvider; 4 | 5 | /** 6 | * Created by kalea on 2017/8/23. 7 | */ 8 | 9 | public class GenericFileProvider extends FileProvider { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/android/GetRemoteXmlThread.java: -------------------------------------------------------------------------------- 1 | package com.vaenow.appupdate.android; 2 | 3 | /** 4 | * Created by LuoWen on 2015/10/27. 5 | */ 6 | public class GetRemoteXmlThread extends Thread { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/android/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Wen Luo 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 | 23 | -------------------------------------------------------------------------------- /src/android/MsgBox.java: -------------------------------------------------------------------------------- 1 | package com.vaenow.appupdate.android; 2 | 3 | import android.app.AlertDialog; 4 | import android.app.AlertDialog.Builder; 5 | import android.app.Dialog; 6 | import android.content.Context; 7 | import android.content.DialogInterface; 8 | import android.content.DialogInterface.OnClickListener; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.widget.ProgressBar; 12 | import org.apache.cordova.LOG; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | /** 18 | * Created by LuoWen on 2016/1/20. 19 | */ 20 | public class MsgBox { 21 | public static final String TAG = "MsgBox"; 22 | private Context mContext; 23 | private MsgHelper msgHelper; 24 | 25 | private Dialog noticeDialog; 26 | private AlertDialog downloadDialog; 27 | private ProgressBar downloadDialogProgress; 28 | private Dialog errorDialog; 29 | 30 | public MsgBox(Context mContext) { 31 | this.mContext = mContext; 32 | this.msgHelper = new MsgHelper(mContext.getPackageName(), mContext.getResources()); 33 | } 34 | 35 | /** 36 | * 显示软件更新对话框 37 | * 38 | * @param onClickListener 39 | */ 40 | public Dialog showNoticeDialog(OnClickListener onClickListener) { 41 | if (noticeDialog == null) { 42 | LOG.d(TAG, "showNoticeDialog"); 43 | // 构造对话框 44 | AlertDialog.Builder builder = new AlertDialog.Builder(mContext); 45 | builder.setTitle(msgHelper.getString(MsgHelper.UPDATE_TITLE)); 46 | builder.setMessage(msgHelper.getString(MsgHelper.UPDATE_MESSAGE)); 47 | // 更新 48 | builder.setPositiveButton(msgHelper.getString(MsgHelper.UPDATE_UPDATE_BTN), onClickListener); 49 | noticeDialog = builder.create(); 50 | } 51 | 52 | if (!noticeDialog.isShowing()) noticeDialog.show(); 53 | 54 | noticeDialog.setCanceledOnTouchOutside(false);// 设置点击屏幕Dialog不消失 55 | return noticeDialog; 56 | } 57 | 58 | 59 | /** 60 | * 显示软件下载对话框 61 | */ 62 | public Map showDownloadDialog(OnClickListener onClickListenerNeg, 63 | OnClickListener onClickListenerPos, 64 | OnClickListener onClickListenerNeu, 65 | boolean showDialog) { 66 | if (downloadDialog == null) { 67 | LOG.d(TAG, "showDownloadDialog"); 68 | 69 | // 构造软件下载对话框 70 | AlertDialog.Builder builder = new Builder(mContext); 71 | builder.setTitle(msgHelper.getString(MsgHelper.UPDATING)); 72 | // 给下载对话框增加进度条 73 | final LayoutInflater inflater = LayoutInflater.from(mContext); 74 | View v = inflater.inflate(msgHelper.getLayout(MsgHelper.APPUPDATE_PROGRESS), null); 75 | 76 | /* 更新进度条 */ 77 | downloadDialogProgress = (ProgressBar) v.findViewById(msgHelper.getId(MsgHelper.UPDATE_PROGRESS)); 78 | builder.setView(v); 79 | // 取消更新 80 | //builder.setNegativeButton(msgHelper.getString("update_cancel"), onClickListener); 81 | //转到后台更新 82 | builder.setNegativeButton(msgHelper.getString(MsgHelper.UPDATE_BG), onClickListenerNeg); 83 | builder.setNeutralButton(msgHelper.getString(MsgHelper.DOWNLOAD_COMPLETE_NEU_BTN), onClickListenerNeu); 84 | builder.setPositiveButton(msgHelper.getString(MsgHelper.DOWNLOAD_COMPLETE_POS_BTN), onClickListenerPos); 85 | downloadDialog = builder.create(); 86 | } 87 | 88 | if (showDialog && !downloadDialog.isShowing()) downloadDialog.show(); 89 | 90 | downloadDialog.setTitle(msgHelper.getString(MsgHelper.UPDATING)); 91 | downloadDialog.setCanceledOnTouchOutside(false);// 设置点击屏幕Dialog不消失 92 | if (downloadDialog.isShowing()) { 93 | downloadDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.VISIBLE); //Update in background 94 | downloadDialog.getButton(DialogInterface.BUTTON_NEUTRAL).setVisibility(View.GONE); //Install Manually 95 | downloadDialog.getButton(DialogInterface.BUTTON_POSITIVE).setVisibility(View.GONE); //Download Again 96 | } 97 | 98 | Map ret = new HashMap(); 99 | ret.put("dialog", downloadDialog); 100 | ret.put("progress", downloadDialogProgress); 101 | return ret; 102 | } 103 | 104 | /** 105 | * 错误提示窗口 106 | * 107 | * @param errorDialogOnClick 108 | */ 109 | public Dialog showErrorDialog(OnClickListener errorDialogOnClick) { 110 | if (this.errorDialog == null) { 111 | LOG.d(TAG, "initErrorDialog"); 112 | // 构造对话框 113 | AlertDialog.Builder builder = new AlertDialog.Builder(mContext); 114 | builder.setTitle(msgHelper.getString(MsgHelper.UPDATE_ERROR_TITLE)); 115 | builder.setMessage(msgHelper.getString(MsgHelper.UPDATE_ERROR_MESSAGE)); 116 | // 更新 117 | builder.setPositiveButton(msgHelper.getString(MsgHelper.UPDATE_ERROR_YES_BTN), errorDialogOnClick); 118 | errorDialog = builder.create(); 119 | } 120 | 121 | if (!errorDialog.isShowing()) errorDialog.show(); 122 | 123 | return errorDialog; 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/android/MsgHelper.java: -------------------------------------------------------------------------------- 1 | package com.vaenow.appupdate.android; 2 | 3 | import android.content.res.Resources; 4 | 5 | /** 6 | * Created by LuoWen on 16/9/16. 7 | */ 8 | public class MsgHelper { 9 | private String packageName; 10 | private Resources resources; 11 | 12 | public static String UPDATE_TITLE = "update_title"; 13 | public static String UPDATE_MESSAGE = "update_message"; 14 | public static String UPDATE_UPDATE_BTN = "update_update_btn"; 15 | public static String APPUPDATE_PROGRESS = "appupdate_progress"; 16 | public static String UPDATE_PROGRESS = "update_progress"; 17 | public static String UPDATING = "updating"; 18 | public static String UPDATE_BG = "update_bg"; 19 | public static String DOWNLOAD_COMPLETE_TITLE = "download_complete_title"; 20 | public static String DOWNLOAD_COMPLETE_POS_BTN = "download_complete_pos_btn"; 21 | public static String DOWNLOAD_COMPLETE_NEU_BTN = "download_complete_neu_btn"; 22 | public static String UPDATE_ERROR_TITLE = "update_error_title"; 23 | public static String UPDATE_ERROR_MESSAGE = "update_error_message"; 24 | public static String UPDATE_ERROR_YES_BTN = "update_error_yes_btn"; 25 | 26 | 27 | MsgHelper(String packageName, Resources resources) { 28 | this.packageName = packageName; 29 | this.resources = resources; 30 | } 31 | 32 | public int getId(String name) { 33 | return resources.getIdentifier(name, "id", packageName); 34 | } 35 | 36 | public int getString(String name) { 37 | return resources.getIdentifier(name, "string", packageName); 38 | } 39 | 40 | public int getLayout(String name) { 41 | return resources.getIdentifier(name, "layout", packageName); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/android/ParseXmlService.java: -------------------------------------------------------------------------------- 1 | package com.vaenow.appupdate.android; 2 | 3 | import org.w3c.dom.Document; 4 | import org.w3c.dom.Element; 5 | import org.w3c.dom.Node; 6 | import org.w3c.dom.NodeList; 7 | 8 | import javax.xml.parsers.DocumentBuilder; 9 | import javax.xml.parsers.DocumentBuilderFactory; 10 | import java.io.InputStream; 11 | import java.util.HashMap; 12 | 13 | /** 14 | * Created by LuoWen on 2015/10/27. 15 | */ 16 | public class ParseXmlService { 17 | public HashMap parseXml(InputStream inStream) throws Exception { 18 | HashMap hashMap = new HashMap(); 19 | 20 | // 实例化一个文档构建器工厂 21 | DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 22 | // 通过文档构建器工厂获取一个文档构建器 23 | DocumentBuilder builder = factory.newDocumentBuilder(); 24 | // 通过文档通过文档构建器构建一个文档实例 25 | Document document = builder.parse(inStream); 26 | //获取XML文件根节点 27 | Element root = document.getDocumentElement(); 28 | //获得所有子节点 29 | NodeList childNodes = root.getChildNodes(); 30 | for (int j = 0; j < childNodes.getLength(); j++) { 31 | //遍历子节点 32 | Node childNode = (Node) childNodes.item(j); 33 | if (childNode.getNodeType() == Node.ELEMENT_NODE) { 34 | Element childElement = (Element) childNode; 35 | //版本号 36 | if ("version".equals(childElement.getNodeName())) { 37 | hashMap.put("version", childElement.getFirstChild().getNodeValue()); 38 | } 39 | //软件名称 40 | else if (("name".equals(childElement.getNodeName()))) { 41 | hashMap.put("name", childElement.getFirstChild().getNodeValue()); 42 | } 43 | //下载地址 44 | else if (("url".equals(childElement.getNodeName()))) { 45 | hashMap.put("url", childElement.getFirstChild().getNodeValue()); 46 | } 47 | } 48 | } 49 | return hashMap; 50 | } 51 | } -------------------------------------------------------------------------------- /src/android/UIValues.java: -------------------------------------------------------------------------------- 1 | package com.vaenow.appupdate.android; 2 | 3 | /** 4 | * Created by Administrator on 2016/1/20. 5 | */ 6 | public class UIValues { 7 | private String updateLatest; 8 | private String updateTitle; 9 | private String updateMessage; 10 | private String updateUpdateBtn; 11 | private String updating; 12 | private String updateCancel; 13 | private String updateErrorTitle; 14 | private String updateErrorMessage; 15 | private String updateErrorYesBtn; 16 | 17 | public String getUpdateLatest() { 18 | return updateLatest; 19 | } 20 | 21 | public void setUpdateLatest(String updateLatest) { 22 | this.updateLatest = updateLatest; 23 | } 24 | 25 | public String getUpdateTitle() { 26 | return updateTitle; 27 | } 28 | 29 | public void setUpdateTitle(String updateTitle) { 30 | this.updateTitle = updateTitle; 31 | } 32 | 33 | public String getUpdateMessage() { 34 | return updateMessage; 35 | } 36 | 37 | public void setUpdateMessage(String updateMessage) { 38 | this.updateMessage = updateMessage; 39 | } 40 | 41 | public String getUpdateUpdateBtn() { 42 | return updateUpdateBtn; 43 | } 44 | 45 | public void setUpdateUpdateBtn(String updateUpdateBtn) { 46 | this.updateUpdateBtn = updateUpdateBtn; 47 | } 48 | 49 | public String getUpdating() { 50 | return updating; 51 | } 52 | 53 | public void setUpdating(String updating) { 54 | this.updating = updating; 55 | } 56 | 57 | public String getUpdateCancel() { 58 | return updateCancel; 59 | } 60 | 61 | public void setUpdateCancel(String updateCancel) { 62 | this.updateCancel = updateCancel; 63 | } 64 | 65 | public String getUpdateErrorTitle() { 66 | return updateErrorTitle; 67 | } 68 | 69 | public void setUpdateErrorTitle(String updateErrorTitle) { 70 | this.updateErrorTitle = updateErrorTitle; 71 | } 72 | 73 | public String getUpdateErrorMessage() { 74 | return updateErrorMessage; 75 | } 76 | 77 | public void setUpdateErrorMessage(String updateErrorMessage) { 78 | this.updateErrorMessage = updateErrorMessage; 79 | } 80 | 81 | public String getUpdateErrorYesBtn() { 82 | return updateErrorYesBtn; 83 | } 84 | 85 | public void setUpdateErrorYesBtn(String updateErrorYesBtn) { 86 | this.updateErrorYesBtn = updateErrorYesBtn; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/android/UpdateManager.java: -------------------------------------------------------------------------------- 1 | package com.vaenow.appupdate.android; 2 | 3 | import android.app.AlertDialog; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.content.DialogInterface.OnClickListener; 7 | import android.os.Handler; 8 | import android.widget.ProgressBar; 9 | import org.apache.cordova.CallbackContext; 10 | import org.apache.cordova.CordovaInterface; 11 | import org.apache.cordova.LOG; 12 | import org.json.JSONArray; 13 | import org.json.JSONObject; 14 | import org.json.JSONException; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | /** 21 | * Created by LuoWen on 2015/10/27. 22 | *

23 | * Thanks @coolszy 24 | */ 25 | public class UpdateManager { 26 | public static final String TAG = "UpdateManager"; 27 | 28 | /* 29 | * 远程的版本文件格式 30 | * 31 | * 2222 32 | * name 33 | * http://192.168.3.102/android.apk 34 | * 35 | */ 36 | private String updateXmlUrl; 37 | private JSONObject options; 38 | private JSONArray args; 39 | private CordovaInterface cordova; 40 | private CallbackContext callbackContext; 41 | private String packageName; 42 | private Context mContext; 43 | private MsgBox msgBox; 44 | private Boolean isDownloading = false; 45 | private List queue = new ArrayList(1); 46 | private CheckUpdateThread checkUpdateThread; 47 | private DownloadApkThread downloadApkThread; 48 | 49 | public UpdateManager(Context context, CordovaInterface cordova) { 50 | this.cordova = cordova; 51 | this.mContext = context; 52 | packageName = mContext.getPackageName(); 53 | msgBox = new MsgBox(mContext); 54 | } 55 | 56 | public UpdateManager(JSONArray args, CallbackContext callbackContext, Context context, JSONObject options) { 57 | this(args, callbackContext, context, "http://192.168.3.102:8080/update_apk/version.xml", options); 58 | } 59 | 60 | public UpdateManager(JSONArray args, CallbackContext callbackContext, Context context, String updateUrl, JSONObject options) { 61 | this.args = args; 62 | this.callbackContext = callbackContext; 63 | this.updateXmlUrl = updateUrl; 64 | this.options = options; 65 | this.mContext = context; 66 | packageName = mContext.getPackageName(); 67 | msgBox = new MsgBox(mContext); 68 | } 69 | 70 | public UpdateManager options(JSONArray args, CallbackContext callbackContext) 71 | throws JSONException { 72 | this.args = args; 73 | this.callbackContext = callbackContext; 74 | this.updateXmlUrl = args.getString(0); 75 | this.options = args.getJSONObject(1); 76 | return this; 77 | } 78 | 79 | private Handler mHandler = new Handler() { 80 | @Override 81 | public void handleMessage(android.os.Message msg) { 82 | super.handleMessage(msg); 83 | 84 | switch (msg.what) { 85 | case Constants.NETWORK_ERROR: 86 | //暂时隐藏错误 87 | //msgBox.showErrorDialog(errorDialogOnClick); 88 | callbackContext.error(Utils.makeJSON(Constants.NETWORK_ERROR, "network error")); 89 | break; 90 | case Constants.VERSION_COMPARE_START: 91 | compareVersions(); 92 | break; 93 | case Constants.DOWNLOAD_CLICK_START: 94 | emitNoticeDialogOnClick(); 95 | break; 96 | case Constants.DOWNLOAD_FINISH: 97 | isDownloading = false; 98 | break; 99 | case Constants.VERSION_UPDATING: 100 | callbackContext.success(Utils.makeJSON(Constants.VERSION_UPDATING, "success, version updating.")); 101 | break; 102 | case Constants.VERSION_NEED_UPDATE: 103 | callbackContext.success(Utils.makeJSON(Constants.VERSION_NEED_UPDATE, "success, need date.")); 104 | break; 105 | case Constants.VERSION_UP_TO_UPDATE: 106 | callbackContext.success(Utils.makeJSON(Constants.VERSION_UP_TO_UPDATE, "success, up to date.")); 107 | break; 108 | case Constants.VERSION_COMPARE_FAIL: 109 | callbackContext.error(Utils.makeJSON(Constants.VERSION_COMPARE_FAIL, "version compare fail")); 110 | break; 111 | case Constants.VERSION_RESOLVE_FAIL: 112 | callbackContext.error(Utils.makeJSON(Constants.VERSION_RESOLVE_FAIL, "version resolve fail")); 113 | break; 114 | case Constants.REMOTE_FILE_NOT_FOUND: 115 | callbackContext.error(Utils.makeJSON(Constants.REMOTE_FILE_NOT_FOUND, "remote file not found")); 116 | break; 117 | default: 118 | callbackContext.error(Utils.makeJSON(Constants.UNKNOWN_ERROR, "unknown error")); 119 | } 120 | 121 | } 122 | }; 123 | 124 | /** 125 | * 检测软件更新 126 | */ 127 | public void checkUpdate() { 128 | LOG.d(TAG, "checkUpdate.."); 129 | 130 | checkUpdateThread = new CheckUpdateThread(mContext, mHandler, queue, packageName, updateXmlUrl, options); 131 | this.cordova.getThreadPool().execute(checkUpdateThread); 132 | //new Thread(checkUpdateThread).start(); 133 | } 134 | 135 | /** 136 | * Permissions denied 137 | */ 138 | public void permissionDenied(String errMsg) { 139 | LOG.d(TAG, "permissionsDenied.."); 140 | 141 | callbackContext.error(Utils.makeJSON(Constants.PERMISSION_DENIED, errMsg)); 142 | } 143 | 144 | /** 145 | * 对比版本号 146 | */ 147 | private void compareVersions() { 148 | Version version = queue.get(0); 149 | int versionCodeLocal = version.getLocal(); 150 | int versionCodeRemote = version.getRemote(); 151 | 152 | boolean skipPromptDialog = false; 153 | try { 154 | skipPromptDialog = options.getBoolean("skipPromptDialog"); 155 | } catch (JSONException e) {} 156 | 157 | boolean skipProgressDialog = false; 158 | try { 159 | skipProgressDialog = options.getBoolean("skipProgressDialog"); 160 | } catch (JSONException e) {} 161 | 162 | //比对版本号 163 | //检查软件是否有更新版本 164 | if (versionCodeLocal < versionCodeRemote) { 165 | if (isDownloading) { 166 | msgBox.showDownloadDialog(null, null, null, !skipProgressDialog); 167 | mHandler.sendEmptyMessage(Constants.VERSION_UPDATING); 168 | } else { 169 | LOG.d(TAG, "need update"); 170 | if (skipPromptDialog) { 171 | mHandler.sendEmptyMessage(Constants.DOWNLOAD_CLICK_START); 172 | } else { 173 | // 显示提示对话框 174 | msgBox.showNoticeDialog(noticeDialogOnClick); 175 | mHandler.sendEmptyMessage(Constants.VERSION_NEED_UPDATE); 176 | } 177 | } 178 | } else { 179 | mHandler.sendEmptyMessage(Constants.VERSION_UP_TO_UPDATE); 180 | // Do not show Toast 181 | //Toast.makeText(mContext, getString("update_latest"), Toast.LENGTH_LONG).show(); 182 | } 183 | } 184 | 185 | private OnClickListener noticeDialogOnClick = new OnClickListener() { 186 | @Override 187 | public void onClick(DialogInterface dialog, int which) { 188 | dialog.dismiss(); 189 | mHandler.sendEmptyMessage(Constants.DOWNLOAD_CLICK_START); 190 | } 191 | }; 192 | 193 | private void emitNoticeDialogOnClick() { 194 | isDownloading = true; 195 | 196 | boolean skipProgressDialog = false; 197 | try { 198 | skipProgressDialog = options.getBoolean("skipProgressDialog"); 199 | } catch (JSONException e) {} 200 | 201 | // 显示下载对话框 202 | Map ret = msgBox.showDownloadDialog( 203 | downloadDialogOnClickNeg, 204 | downloadDialogOnClickPos, 205 | downloadDialogOnClickNeu, 206 | !skipProgressDialog); 207 | 208 | // 下载文件 209 | downloadApk((AlertDialog) ret.get("dialog"), (ProgressBar) ret.get("progress")); 210 | } 211 | 212 | /** 213 | * 手动安装 214 | * Download again 215 | */ 216 | private OnClickListener downloadDialogOnClickNeu = new OnClickListener() { 217 | @Override 218 | public void onClick(DialogInterface dialog, int which) { 219 | //Implemented in DownloadHandler.java 220 | } 221 | }; 222 | /** 223 | * 重新下载 224 | * Download again 225 | */ 226 | private OnClickListener downloadDialogOnClickPos = new OnClickListener() { 227 | @Override 228 | public void onClick(DialogInterface dialog, int which) { 229 | dialog.dismiss(); 230 | mHandler.sendEmptyMessage(Constants.DOWNLOAD_CLICK_START); 231 | } 232 | }; 233 | /** 234 | * 转到后台更新 235 | * Update in background 236 | */ 237 | private OnClickListener downloadDialogOnClickNeg = new OnClickListener() { 238 | @Override 239 | public void onClick(DialogInterface dialog, int which) { 240 | dialog.dismiss(); 241 | // 设置取消状态 242 | //downloadApkThread.cancelBuildUpdate(); 243 | } 244 | }; 245 | 246 | private OnClickListener errorDialogOnClick = new OnClickListener() { 247 | @Override 248 | public void onClick(DialogInterface dialog, int which) { 249 | dialog.dismiss(); 250 | } 251 | }; 252 | 253 | /** 254 | * 下载apk文件 255 | * 256 | * @param mProgress 257 | * @param mDownloadDialog 258 | */ 259 | private void downloadApk(AlertDialog mDownloadDialog, ProgressBar mProgress) { 260 | LOG.d(TAG, "downloadApk" + mProgress); 261 | 262 | // 启动新线程下载软件 263 | downloadApkThread = new DownloadApkThread(mContext, mHandler, mProgress, mDownloadDialog, checkUpdateThread.getMHashMap(), options); 264 | this.cordova.getThreadPool().execute(downloadApkThread); 265 | // new Thread(downloadApkThread).start(); 266 | } 267 | 268 | } 269 | -------------------------------------------------------------------------------- /src/android/Utils.java: -------------------------------------------------------------------------------- 1 | package com.vaenow.appupdate.android; 2 | 3 | import org.json.JSONException; 4 | import org.json.JSONObject; 5 | 6 | /** 7 | * Created by LuoWen on 16/7/22. 8 | */ 9 | public class Utils { 10 | 11 | static JSONObject makeJSON(int code, Object msg) { 12 | JSONObject json = new JSONObject(); 13 | 14 | try { 15 | json.put("code", code); 16 | json.put("msg", msg); 17 | } catch (JSONException e) { 18 | e.printStackTrace(); 19 | } 20 | 21 | return json; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/android/Version.java: -------------------------------------------------------------------------------- 1 | package com.vaenow.appupdate.android; 2 | 3 | /** 4 | * Created by LuoWen on 2015/12/14. 5 | */ 6 | public class Version { 7 | private int local; 8 | private int remote; 9 | 10 | public Version(int local, int remote) { 11 | this.local = local; 12 | this.remote = remote; 13 | } 14 | 15 | public int getLocal() { 16 | return local; 17 | } 18 | 19 | public int getRemote() { 20 | return remote; 21 | } 22 | } -------------------------------------------------------------------------------- /syncFromDemo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # sync *.java 4 | cp -r ../cordova-plugin-app-update-demo/platforms/android/src/com/vaenow/appupdate/android/* src/android/ 5 | 6 | # sync *.xml 7 | cp -r ../cordova-plugin-app-update-demo/platforms/android/res/values* res/ 8 | 9 | # clean 10 | rm res/values/strings.xml 11 | 12 | -------------------------------------------------------------------------------- /www/AppUpdate.js: -------------------------------------------------------------------------------- 1 | var exec = require('cordova/exec'); 2 | 3 | /** 4 | * Check if there is an update to the App. 5 | * 6 | * This function can be called in three ways: 7 | * 1. checkAppUpdate(updateUrl) 8 | * 2. checkAppUpdate(updateUrl, options) 9 | * 3. checkAppUpdate(sucessCallback, errorCallback, updateUrl, options) 10 | * 11 | * @param successOrUrl The success callback or the URL where the update data is located 12 | * @param errorOrOptions The function called on error or the authentication options 13 | * @param updateUrl The URL where the update data is located 14 | * @param options An object that may contain the authentication options 15 | */ 16 | exports.checkAppUpdate = function(successOrUrl, errorOrOptions, updateUrl, options) { 17 | // If the update URL hasnt been set in the updateUrl then assume no callbacks passed 18 | var successCallback = updateUrl ? successOrUrl : null; 19 | var errorCallback = updateUrl ? errorOrOptions : null; 20 | 21 | // This handles case 2, where there is an updateURL and options set 22 | if ( !updateUrl && typeof errorOrOptions === 'object' ) { 23 | options = errorOrOptions; 24 | } 25 | 26 | // If there is no updateUrl then assume that the URL is the first paramater 27 | updateUrl = updateUrl ? updateUrl : successOrUrl; 28 | 29 | options = options ? options : {}; 30 | 31 | exec(successCallback, errorCallback, "AppUpdate", "checkAppUpdate", [updateUrl, options]); 32 | }; 33 | --------------------------------------------------------------------------------