| @ -0,0 +1,10 @@ | ||||
| *.iml | ||||
| .gradle | ||||
| /local.properties | ||||
| /.idea/libraries | ||||
| /.idea/modules.xml | ||||
| /.idea/workspace.xml | ||||
| .DS_Store | ||||
| /build | ||||
| /captures | ||||
| .externalNativeBuild | ||||
| @ -0,0 +1,57 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="WizardSettings"> | ||||
|     <option name="children"> | ||||
|       <map> | ||||
|         <entry key="imageWizard"> | ||||
|           <value> | ||||
|             <PersistentState> | ||||
|               <option name="children"> | ||||
|                 <map> | ||||
|                   <entry key="imageAssetPanel"> | ||||
|                     <value> | ||||
|                       <PersistentState> | ||||
|                         <option name="children"> | ||||
|                           <map> | ||||
|                             <entry key="launcher"> | ||||
|                               <value> | ||||
|                                 <PersistentState> | ||||
|                                   <option name="children"> | ||||
|                                     <map> | ||||
|                                       <entry key="foregroundImage"> | ||||
|                                         <value> | ||||
|                                           <PersistentState> | ||||
|                                             <option name="values"> | ||||
|                                               <map> | ||||
|                                                 <entry key="scalingPercent" value="50" /> | ||||
|                                               </map> | ||||
|                                             </option> | ||||
|                                           </PersistentState> | ||||
|                                         </value> | ||||
|                                       </entry> | ||||
|                                     </map> | ||||
|                                   </option> | ||||
|                                   <option name="values"> | ||||
|                                     <map> | ||||
|                                       <entry key="backgroundAssetType" value="COLOR" /> | ||||
|                                       <entry key="backgroundColor" value="ffffff" /> | ||||
|                                       <entry key="foregroundImage" value="C:\app_updater.png" /> | ||||
|                                     </map> | ||||
|                                   </option> | ||||
|                                 </PersistentState> | ||||
|                               </value> | ||||
|                             </entry> | ||||
|                           </map> | ||||
|                         </option> | ||||
|                       </PersistentState> | ||||
|                     </value> | ||||
|                   </entry> | ||||
|                 </map> | ||||
|               </option> | ||||
|             </PersistentState> | ||||
|           </value> | ||||
|         </entry> | ||||
|       </map> | ||||
|     </option> | ||||
|   </component> | ||||
| </project> | ||||
| @ -0,0 +1,29 @@ | ||||
| <component name="ProjectCodeStyleConfiguration"> | ||||
|   <code_scheme name="Project" version="173"> | ||||
|     <Objective-C-extensions> | ||||
|       <file> | ||||
|         <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" /> | ||||
|         <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" /> | ||||
|         <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" /> | ||||
|         <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" /> | ||||
|         <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" /> | ||||
|         <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" /> | ||||
|         <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" /> | ||||
|         <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" /> | ||||
|         <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" /> | ||||
|       </file> | ||||
|       <class> | ||||
|         <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" /> | ||||
|         <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" /> | ||||
|         <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" /> | ||||
|         <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" /> | ||||
|         <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" /> | ||||
|         <option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" /> | ||||
|       </class> | ||||
|       <extensions> | ||||
|         <pair source="cpp" header="h" fileNamingConvention="NONE" /> | ||||
|         <pair source="c" header="h" fileNamingConvention="NONE" /> | ||||
|       </extensions> | ||||
|     </Objective-C-extensions> | ||||
|   </code_scheme> | ||||
| </component> | ||||
| @ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8"> | ||||
|     <file url="PROJECT" charset="UTF-8" /> | ||||
|   </component> | ||||
| </project> | ||||
| @ -0,0 +1,20 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="GradleSettings"> | ||||
|     <option name="linkedExternalProjectsSettings"> | ||||
|       <GradleProjectSettings> | ||||
|         <option name="distributionType" value="DEFAULT_WRAPPED" /> | ||||
|         <option name="externalProjectPath" value="$PROJECT_DIR$" /> | ||||
|         <option name="modules"> | ||||
|           <set> | ||||
|             <option value="$PROJECT_DIR$" /> | ||||
|             <option value="$PROJECT_DIR$/app" /> | ||||
|             <option value="$PROJECT_DIR$/app-dialog" /> | ||||
|             <option value="$PROJECT_DIR$/app-updater" /> | ||||
|           </set> | ||||
|         </option> | ||||
|         <option name="resolveModulePerSourceSet" value="false" /> | ||||
|       </GradleProjectSettings> | ||||
|     </option> | ||||
|   </component> | ||||
| </project> | ||||
| @ -0,0 +1,34 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="NullableNotNullManager"> | ||||
|     <option name="myDefaultNullable" value="android.support.annotation.Nullable" /> | ||||
|     <option name="myDefaultNotNull" value="android.support.annotation.NonNull" /> | ||||
|     <option name="myNullables"> | ||||
|       <value> | ||||
|         <list size="5"> | ||||
|           <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" /> | ||||
|           <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" /> | ||||
|           <item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" /> | ||||
|           <item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" /> | ||||
|           <item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" /> | ||||
|         </list> | ||||
|       </value> | ||||
|     </option> | ||||
|     <option name="myNotNulls"> | ||||
|       <value> | ||||
|         <list size="4"> | ||||
|           <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" /> | ||||
|           <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" /> | ||||
|           <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" /> | ||||
|           <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" /> | ||||
|         </list> | ||||
|       </value> | ||||
|     </option> | ||||
|   </component> | ||||
|   <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK"> | ||||
|     <output url="file://$PROJECT_DIR$/build/classes" /> | ||||
|   </component> | ||||
|   <component name="ProjectType"> | ||||
|     <option name="id" value="Android" /> | ||||
|   </component> | ||||
| </project> | ||||
| @ -0,0 +1,12 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="RunConfigurationProducerService"> | ||||
|     <option name="ignoredProducers"> | ||||
|       <set> | ||||
|         <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" /> | ||||
|         <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" /> | ||||
|         <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" /> | ||||
|       </set> | ||||
|     </option> | ||||
|   </component> | ||||
| </project> | ||||
| @ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="VcsDirectoryMappings"> | ||||
|     <mapping directory="$PROJECT_DIR$" vcs="Git" /> | ||||
|   </component> | ||||
| </project> | ||||
| @ -0,0 +1,23 @@ | ||||
| 
 | ||||
|      The MIT License (MIT) | ||||
|      Copyright (c) 2017 Jenly Yu | ||||
|      https://github.com/jenly1314 | ||||
| 
 | ||||
|      Permission is hereby granted, free of charge, to any person obtaining | ||||
|      a copy of this software and associated documentation files | ||||
|      (the "Software"), to deal in the Software without restriction, including | ||||
|      without limitation the rights to use, copy, modify, merge, publish, | ||||
|      distribute, sublicense, and/or sell copies of the Software, and to permit | ||||
|      persons to whom the Software is furnished to do so, subject to the | ||||
|      following conditions: | ||||
| 
 | ||||
|      The above copyright notice and this permission notice shall be included | ||||
|      in all copies or substantial portions of the Software. | ||||
| 
 | ||||
|      THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|      IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|      FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|      AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|      LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||||
|      FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||||
|      DEALINGS IN THE SOFTWARE. | ||||
| @ -0,0 +1,142 @@ | ||||
| # AppUpdater | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| [](https://raw.githubusercontent.com/jenly1314/AppUpdater/master/app/release/app-release.apk) | ||||
| [](https://jitpack.io/#jenly1314/AppUpdater) | ||||
| [](https://opensource.org/licenses/mit-license.php) | ||||
| [](http://blog.csdn.net/jenly121) | ||||
| 
 | ||||
| AppUpdater for Android 是一个专注于App更新一键傻瓜式集成的开源库,主要包括app-updater和app-dialog。 | ||||
| > 下载更新和弹框提示分开,是因为这本来就是两个逻辑。完全独立开来能有效的解耦。 | ||||
| * app-updater 主要负责后台下载更新App,无需担心下载时各种配置相关的细节,一键傻瓜式升级。 | ||||
| * app-dialog 主要是提供常用的Dialog和DialogFragment,简化弹框提示,样式支持高度自定义。 | ||||
| > app-updater + app-dialog 配合使用,谁用谁知道。 | ||||
| 
 | ||||
| 
 | ||||
| ## 功能介绍 | ||||
| - [x] 专注于App更新一键傻瓜式升级 | ||||
| - [x] 支持下载监听 | ||||
| - [x] 支持下载失败,重新下载 | ||||
| - [x] 支持通知栏提示内容和过程全部可配置 | ||||
| - [x] 支持Android O | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| ## Gif 展示 | ||||
|  | ||||
| 
 | ||||
| ## 引入 | ||||
| 
 | ||||
| ### Maven: | ||||
| ```maven | ||||
|     //app-updater | ||||
|     <dependency> | ||||
|       <groupId>com.king.app</groupId> | ||||
|       <artifactId>app-updater</artifactId> | ||||
|       <version>1.0</version> | ||||
|       <type>pom</type> | ||||
|     </dependency> | ||||
|      | ||||
|     //app-dialog | ||||
|     <dependency> | ||||
|       <groupId>com.king.app</groupId> | ||||
|       <artifactId>app-dialog</artifactId> | ||||
|       <version>1.0</version> | ||||
|       <type>pom</type> | ||||
|     </dependency> | ||||
| ``` | ||||
| ### Gradle: | ||||
| ```gradle | ||||
|     //app-updater | ||||
|     compile 'com.king.app:app-updater:1.0' | ||||
|      | ||||
|     //app-dialog | ||||
|     compile 'com.king.app:app-dialog:1.0' | ||||
| ``` | ||||
| ### Lvy: | ||||
| ```lvy | ||||
|     //app-updater | ||||
|     <dependency org='com.king.app' name='app-dialog' rev='1.0'> | ||||
|       <artifact name='$AID' ext='pom'></artifact> | ||||
|     </dependency> | ||||
|      | ||||
|     //app-dialog | ||||
|     <dependency org='com.king.app' name='app-dialog' rev='1.0'> | ||||
|       <artifact name='$AID' ext='pom'></artifact> | ||||
|     </dependency> | ||||
| ``` | ||||
| 
 | ||||
| ###### 如果Gradle出现compile失败的情况,可以在Project的build.gradle里面添加如下:(也可以使用上面的GitPack来complie) | ||||
| ```gradle | ||||
|     allprojects { | ||||
|         repositories { | ||||
|             //... | ||||
|             maven { url 'https://dl.bintray.com/jenly/maven' } | ||||
|         } | ||||
|     } | ||||
| ``` | ||||
| 
 | ||||
| ## 示例 | ||||
| 
 | ||||
| ```Java | ||||
|     //一句代码,傻瓜式更新 | ||||
|     new AppUpdater(getContext(),url).start(); | ||||
| ``` | ||||
| ```Java | ||||
|     //简单弹框升级 | ||||
|     AppDialogConfig config = new AppDialogConfig(); | ||||
|     config.setTitle("简单弹框升级") | ||||
|             .setOk("升级") | ||||
|             .setContent("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、") | ||||
|             .setOnClickOk(new View.OnClickListener() { | ||||
|                 @Override | ||||
|                 public void onClick(View v) { | ||||
|                     new AppUpdater.Builder() | ||||
|                             .serUrl(mUrl) | ||||
|                             .setFilename("AppUpdater.apk") | ||||
|                             .build(getContext()) | ||||
|                             .start(); | ||||
|                     AppDialog.INSTANCE.dismissDialog(); | ||||
|                 } | ||||
|             }); | ||||
|     AppDialog.INSTANCE.showDialog(getContext(),config); | ||||
| ``` | ||||
| ```Java | ||||
|     //简单DialogFragment升级 | ||||
|     AppDialogConfig config = new AppDialogConfig(); | ||||
|     config.setTitle("简单DialogFragment升级") | ||||
|             .setOk("升级") | ||||
|             .setContent("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、") | ||||
|             .setOnClickOk(new View.OnClickListener() { | ||||
|                 @Override | ||||
|                 public void onClick(View v) { | ||||
|                     new AppUpdater.Builder() | ||||
|                             .serUrl(mUrl) | ||||
|                             .setFilename("AppUpdater.apk") | ||||
|                             .build(getContext()) | ||||
|                             .start(); | ||||
|                     AppDialog.INSTANCE.dismissDialogFragment(getSupportFragmentManager()); | ||||
|                 } | ||||
|             }); | ||||
|     AppDialog.INSTANCE.showDialogFragment(getSupportFragmentManager(),config); | ||||
| 
 | ||||
| ``` | ||||
| 
 | ||||
| 更多使用示例请查看[App](app)。 | ||||
| 
 | ||||
| 
 | ||||
| ## 关于我 | ||||
|    Name: <a title="关于作者" href="https://about.me/jenly1314" target="_blank">Jenly</a> | ||||
| 
 | ||||
|    Email: <a title="欢迎邮件与我交流" href="mailto:jenly1314@gmail.com" target="_blank">jenly1314#gmail.com</a> / <a title="给我发邮件" href="mailto:jenly1314@vip.qq.com" target="_blank">jenly1314#vip.qq.com</a> | ||||
| 
 | ||||
|    CSDN: <a title="CSDN博客" href="http://blog.csdn.net/jenly121" target="_blank">jenly121</a> | ||||
| 
 | ||||
|    Github: <a title="Github开源项目" href="https://github.com/jenly1314" target="_blank">jenly1314</a> | ||||
| 
 | ||||
|    微信公众号: | ||||
| 
 | ||||
|     | ||||
| 
 | ||||
|    加入QQ群: <a title="点击加入QQ群" href="http://shang.qq.com/wpa/qunwpa?idkey=8fcc6a2f88552ea44b1411582c94fd124f7bb3ec227e2a400dbbfaad3dc2f5ad" target="_blank">20867961</a> | ||||
| @ -0,0 +1 @@ | ||||
| /build | ||||
| @ -0,0 +1,10 @@ | ||||
| apply plugin: 'com.novoda.bintray-release' | ||||
| //添加 | ||||
| publish { | ||||
|     userOrg = 'jenly'//bintray.com用户名 | ||||
|     groupId = 'com.king.app'//jcenter上的路径 | ||||
|     artifactId = 'app-dialog'//项目名称 | ||||
|     publishVersion = '1.0'//版本号 | ||||
|     desc = 'AppDialog for Android'//描述 | ||||
|     website = 'https://github.com/jenly1314/AppUpdater'//网站 | ||||
| } | ||||
| @ -0,0 +1,42 @@ | ||||
| apply plugin: 'com.android.library' | ||||
| apply from: 'bintray.gradle' | ||||
| 
 | ||||
| android { | ||||
|     compileSdkVersion 27 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     defaultConfig { | ||||
|         minSdkVersion 15 | ||||
|         targetSdkVersion 27 | ||||
|         versionCode 1 | ||||
|         versionName "1.0" | ||||
| 
 | ||||
|         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     buildTypes { | ||||
|         release { | ||||
|             minifyEnabled false | ||||
|             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| dependencies { | ||||
|     implementation fileTree(dir: 'libs', include: ['*.jar']) | ||||
| 
 | ||||
|     compileOnly 'com.android.support:appcompat-v7:27.1.1' | ||||
|     testImplementation 'junit:junit:4.12' | ||||
|     androidTestImplementation 'com.android.support.test:runner:1.0.2' | ||||
|     androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| @ -0,0 +1,21 @@ | ||||
| # Add project specific ProGuard rules here. | ||||
| # You can control the set of applied configuration files using the | ||||
| # proguardFiles setting in build.gradle. | ||||
| # | ||||
| # For more details, see | ||||
| #   http://developer.android.com/guide/developing/tools/proguard.html | ||||
| 
 | ||||
| # If your project uses WebView with JS, uncomment the following | ||||
| # and specify the fully qualified class name to the JavaScript interface | ||||
| # class: | ||||
| #-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||||
| #   public *; | ||||
| #} | ||||
| 
 | ||||
| # Uncomment this to preserve the line number information for | ||||
| # debugging stack traces. | ||||
| #-keepattributes SourceFile,LineNumberTable | ||||
| 
 | ||||
| # If you keep the line number information, uncomment this to | ||||
| # hide the original source file name. | ||||
| #-renamesourcefileattribute SourceFile | ||||
| @ -0,0 +1,26 @@ | ||||
| package com.king.app.dialog; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.support.test.InstrumentationRegistry; | ||||
| import android.support.test.runner.AndroidJUnit4; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.*; | ||||
| 
 | ||||
| /** | ||||
|  * Instrumented test, which will execute on an Android device. | ||||
|  * | ||||
|  * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> | ||||
|  */ | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| public class ExampleInstrumentedTest { | ||||
|     @Test | ||||
|     public void useAppContext() { | ||||
|         // Context of the app under test.
 | ||||
|         Context appContext = InstrumentationRegistry.getTargetContext(); | ||||
| 
 | ||||
|         assertEquals("com.king.app.dialog.test", appContext.getPackageName()); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,2 @@ | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     package="com.king.app.dialog" /> | ||||
| @ -0,0 +1,194 @@ | ||||
| package com.king.app.dialog; | ||||
| 
 | ||||
| import android.app.Dialog; | ||||
| import android.content.Context; | ||||
| import android.content.DialogInterface; | ||||
| import android.support.annotation.StyleRes; | ||||
| import android.support.v4.app.DialogFragment; | ||||
| import android.support.v4.app.FragmentManager; | ||||
| import android.text.TextUtils; | ||||
| import android.view.KeyEvent; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.Window; | ||||
| import android.view.WindowManager; | ||||
| import android.widget.Button; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
| import com.king.app.dialog.fragment.AppDialogFragment; | ||||
| 
 | ||||
| /** | ||||
|  * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> | ||||
|  */ | ||||
| public enum AppDialog { | ||||
| 
 | ||||
|     INSTANCE; | ||||
| 
 | ||||
|     private final float DEFAULT_WIDTH_RATIO = 0.85f; | ||||
| 
 | ||||
|     private Dialog mDialog; | ||||
| 
 | ||||
|     private String mTag; | ||||
| 
 | ||||
|     //-------------------------------------------
 | ||||
| 
 | ||||
|     public View createAppDialogView(Context context,AppDialogConfig config){ | ||||
|         View view = null; | ||||
|         if(config!=null){ | ||||
|             view = LayoutInflater.from(context).inflate(config.getLayoutId(),null); | ||||
|             TextView tvDialogTitle = view.findViewById(config.getTitleId()); | ||||
|             setText(tvDialogTitle,config.getTitle()); | ||||
| 
 | ||||
|             TextView tvDialogContent = view.findViewById(config.getContentId()); | ||||
|             setText(tvDialogContent,config.getContent()); | ||||
| 
 | ||||
|             Button btnDialogCancel = view.findViewById(config.getCancelId()); | ||||
|             setText(btnDialogCancel,config.getCancel()); | ||||
|             btnDialogCancel.setOnClickListener(config.getOnClickCancel() != null ? config.getOnClickCancel() : mOnClickDismissDialog); | ||||
|             btnDialogCancel.setVisibility(config.isHideCancel() ? View.GONE : View.VISIBLE); | ||||
| 
 | ||||
|             try{ | ||||
|                 //不强制要求要有横线
 | ||||
|                 View line = view.findViewById(R.id.line); | ||||
|                 line.setVisibility(config.isHideCancel() ? View.GONE : View.VISIBLE); | ||||
|             }catch (Exception e){ | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|             Button btnDialogOK = view.findViewById(config.getOkId()); | ||||
|             setText(btnDialogOK,config.getOk()); | ||||
|             btnDialogOK.setOnClickListener(config.getOnClickOk() != null ? config.getOnClickOk() : mOnClickDismissDialog); | ||||
|         } | ||||
| 
 | ||||
|         return view; | ||||
|     } | ||||
| 
 | ||||
|     //-------------------------------------------
 | ||||
| 
 | ||||
|     private View.OnClickListener mOnClickDismissDialog = new View.OnClickListener() { | ||||
|         @Override | ||||
|         public void onClick(View v) { | ||||
|             dismissDialog(); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     private void setText(TextView tv,CharSequence text){ | ||||
|         if(!TextUtils.isEmpty(text)){ | ||||
|             tv.setText(text); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     //-------------------------------------------
 | ||||
| 
 | ||||
|     public void dismissDialogFragment(FragmentManager fragmentManager){ | ||||
|         dismissDialogFragment(fragmentManager,mTag); | ||||
|     } | ||||
| 
 | ||||
|     public void dismissDialogFragment(FragmentManager fragmentManager,String tag){ | ||||
|         if(tag!=null){ | ||||
|             DialogFragment dialogFragment = (DialogFragment) fragmentManager.findFragmentByTag(tag); | ||||
|             dismissDialogFragment(dialogFragment); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void dismissDialogFragment(DialogFragment dialogFragment){ | ||||
|         if(dialogFragment!=null){ | ||||
|             dialogFragment.dismiss(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     //-------------------------------------------
 | ||||
| 
 | ||||
|     public String showDialogFragment(FragmentManager fragmentManager,AppDialogConfig config){ | ||||
|         AppDialogFragment dialogFragment = AppDialogFragment.newInstance(config); | ||||
|         String tag = dialogFragment.getTag() !=null ? dialogFragment.getTag() : dialogFragment.getClass().getSimpleName(); | ||||
|         showDialogFragment(fragmentManager,dialogFragment,tag); | ||||
|         mTag = tag; | ||||
|         return tag; | ||||
|     } | ||||
| 
 | ||||
|     public String showDialogFragment(FragmentManager fragmentManager,DialogFragment dialogFragment){ | ||||
|         String tag = dialogFragment.getTag() !=null ? dialogFragment.getTag() : dialogFragment.getClass().getSimpleName(); | ||||
|         showDialogFragment(fragmentManager,dialogFragment,tag); | ||||
|         mTag = tag; | ||||
|         return tag; | ||||
|     } | ||||
| 
 | ||||
|     public String showDialogFragment(FragmentManager fragmentManager,DialogFragment dialogFragment, String tag) { | ||||
|         dialogFragment.show(fragmentManager,tag); | ||||
|         mTag = tag; | ||||
|         return tag; | ||||
|     } | ||||
| 
 | ||||
|     //-------------------------------------------
 | ||||
| 
 | ||||
|     public void showDialog(Context context,AppDialogConfig config){ | ||||
|         showDialog(context,createAppDialogView(context,config)); | ||||
|     } | ||||
| 
 | ||||
|     public void showDialog(Context context,View contentView){ | ||||
|         showDialog(context,contentView,DEFAULT_WIDTH_RATIO); | ||||
|     } | ||||
| 
 | ||||
|     public void showDialog(Context context,View contentView,boolean isCancel){ | ||||
|         showDialog(context,contentView,R.style.app_dialog,DEFAULT_WIDTH_RATIO,isCancel); | ||||
|     } | ||||
| 
 | ||||
|     public void showDialog(Context context,View contentView,float widthRatio){ | ||||
|         showDialog(context,contentView,widthRatio,true); | ||||
|     } | ||||
| 
 | ||||
|     public void showDialog(Context context,View contentView,float widthRatio,boolean isCancel){ | ||||
|         showDialog(context,contentView,R.style.app_dialog,widthRatio,isCancel); | ||||
|     } | ||||
| 
 | ||||
|     public void showDialog(Context context, View contentView, @StyleRes int resId, float widthRatio){ | ||||
|         showDialog(context,contentView,resId,widthRatio,true); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * | ||||
|      * @param context | ||||
|      * @param contentView | ||||
|      * @param resId Dialog样式 | ||||
|      * @param widthRatio | ||||
|      * @param isCancel 是否可取消(默认为true,false则拦截back键) | ||||
|      */ | ||||
|     public void showDialog(Context context, View contentView, @StyleRes int resId, float widthRatio,final boolean isCancel){ | ||||
|         mDialog = new Dialog(context,resId); | ||||
|         mDialog.setContentView(contentView); | ||||
|         mDialog.setCanceledOnTouchOutside(false); | ||||
|         mDialog.setOnKeyListener(new DialogInterface.OnKeyListener() { | ||||
|             @Override | ||||
|             public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { | ||||
|                 if(keyCode == KeyEvent.KEYCODE_BACK && isCancel){ | ||||
|                     dismissDialog(); | ||||
|                 } | ||||
|                 return true; | ||||
| 
 | ||||
|             } | ||||
|         }); | ||||
|         setDialogWindow(context,mDialog,widthRatio); | ||||
|         mDialog.show(); | ||||
|     } | ||||
| 
 | ||||
|     private void setDialogWindow(Context context,Dialog dialog,float widthRatio){ | ||||
|         Window window = dialog.getWindow(); | ||||
|         WindowManager.LayoutParams lp = window.getAttributes(); | ||||
|         lp.width = (int)(context.getResources().getDisplayMetrics().widthPixels * widthRatio); | ||||
|         window.setAttributes(lp); | ||||
|     } | ||||
| 
 | ||||
|     public void dismissDialog(){ | ||||
|         dismissDialog(mDialog); | ||||
|     } | ||||
| 
 | ||||
|     private void dismissDialog(Dialog dialog){ | ||||
|         if(dialog!=null){ | ||||
|             dialog.dismiss(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     //-------------------------------------------
 | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,156 @@ | ||||
| package com.king.app.dialog; | ||||
| 
 | ||||
| import android.support.annotation.IdRes; | ||||
| import android.support.annotation.LayoutRes; | ||||
| import android.view.View; | ||||
| 
 | ||||
| /** | ||||
|  * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> | ||||
|  */ | ||||
| public class AppDialogConfig { | ||||
| 
 | ||||
|     private @LayoutRes int layoutId = R.layout.app_dialog; | ||||
| 
 | ||||
|     private @IdRes int titleId = R.id.tvDialogTitle; | ||||
| 
 | ||||
|     private @IdRes int contentId = R.id.tvDialogContent; | ||||
| 
 | ||||
|     private @IdRes int cancelId = R.id.btnDialogCancel; | ||||
| 
 | ||||
|     private @IdRes int okId = R.id.btnDialogOK; | ||||
| 
 | ||||
|     private @IdRes int line = R.id.line; | ||||
| 
 | ||||
|     private CharSequence title; | ||||
| 
 | ||||
|     private CharSequence content; | ||||
| 
 | ||||
|     private CharSequence cancel; | ||||
| 
 | ||||
|     private CharSequence ok; | ||||
| 
 | ||||
|     private boolean isHideCancel; | ||||
| 
 | ||||
|     private View.OnClickListener onClickCancel; | ||||
| 
 | ||||
|     private View.OnClickListener onClickOk; | ||||
| 
 | ||||
|     public int getLayoutId() { | ||||
|         return layoutId; | ||||
|     } | ||||
| 
 | ||||
|     public AppDialogConfig setLayoutId(int layoutId) { | ||||
|         this.layoutId = layoutId; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public int getTitleId() { | ||||
|         return titleId; | ||||
|     } | ||||
| 
 | ||||
|     public AppDialogConfig setTitleId(int titleId) { | ||||
|         this.titleId = titleId; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public int getContentId() { | ||||
|         return contentId; | ||||
|     } | ||||
| 
 | ||||
|     public AppDialogConfig setContentId(int contentId) { | ||||
|         this.contentId = contentId; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public int getCancelId() { | ||||
|         return cancelId; | ||||
|     } | ||||
| 
 | ||||
|     public AppDialogConfig setCancelId(int cancelId) { | ||||
|         this.cancelId = cancelId; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public int getOkId() { | ||||
|         return okId; | ||||
|     } | ||||
| 
 | ||||
|     public AppDialogConfig setOkId(int okId) { | ||||
|         this.okId = okId; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public int getLine() { | ||||
|         return line; | ||||
|     } | ||||
| 
 | ||||
|     public AppDialogConfig setLine(int line) { | ||||
|         this.line = line; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public CharSequence getTitle() { | ||||
|         return title; | ||||
|     } | ||||
| 
 | ||||
|     public AppDialogConfig setTitle(CharSequence title) { | ||||
|         this.title = title; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public CharSequence getContent() { | ||||
|         return content; | ||||
|     } | ||||
| 
 | ||||
|     public AppDialogConfig setContent(CharSequence content) { | ||||
|         this.content = content; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public CharSequence getCancel() { | ||||
|         return cancel; | ||||
|     } | ||||
| 
 | ||||
|     public AppDialogConfig setCancel(CharSequence cancel) { | ||||
|         this.cancel = cancel; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public CharSequence getOk() { | ||||
|         return ok; | ||||
|     } | ||||
| 
 | ||||
|     public AppDialogConfig setOk(CharSequence ok) { | ||||
|         this.ok = ok; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public boolean isHideCancel() { | ||||
|         return isHideCancel; | ||||
|     } | ||||
| 
 | ||||
|     public AppDialogConfig setHideCancel(boolean hideCancel) { | ||||
|         isHideCancel = hideCancel; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public View.OnClickListener getOnClickCancel() { | ||||
|         return onClickCancel; | ||||
|     } | ||||
| 
 | ||||
|     public AppDialogConfig setOnClickCancel(View.OnClickListener onClickCancel) { | ||||
|         this.onClickCancel = onClickCancel; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public View.OnClickListener getOnClickOk() { | ||||
|         return onClickOk; | ||||
|     } | ||||
| 
 | ||||
|     public AppDialogConfig setOnClickOk(View.OnClickListener onClickOk) { | ||||
|         this.onClickOk = onClickOk; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,61 @@ | ||||
| package com.king.app.dialog.fragment; | ||||
| 
 | ||||
| import android.os.Bundle; | ||||
| import android.view.View; | ||||
| import android.widget.Button; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
| import com.king.app.dialog.AppDialogConfig; | ||||
| import com.king.app.dialog.R; | ||||
| 
 | ||||
| /** | ||||
|  * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> | ||||
|  */ | ||||
| public class AppDialogFragment extends BaseDialogFragment { | ||||
| 
 | ||||
|     private AppDialogConfig config; | ||||
| 
 | ||||
|     public static AppDialogFragment newInstance(AppDialogConfig config) { | ||||
| 
 | ||||
|         Bundle args = new Bundle(); | ||||
|         AppDialogFragment fragment = new AppDialogFragment(); | ||||
|         fragment.config = config; | ||||
|         fragment.setArguments(args); | ||||
|         return fragment; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int getRootLayoutId() { | ||||
|         if(config == null){ | ||||
|             config = new AppDialogConfig(); | ||||
|         } | ||||
|         return config.getLayoutId(); | ||||
|     } | ||||
| 
 | ||||
|     public void init(View rootView){ | ||||
|         if(config!=null){ | ||||
|             TextView tvDialogTitle = rootView.findViewById(config.getTitleId()); | ||||
|             setText(tvDialogTitle,config.getTitle()); | ||||
| 
 | ||||
|             TextView tvDialogContent = rootView.findViewById(config.getContentId()); | ||||
|             setText(tvDialogContent,config.getContent()); | ||||
| 
 | ||||
|             Button btnDialogCancel = rootView.findViewById(config.getCancelId()); | ||||
|             setText(btnDialogCancel,config.getCancel()); | ||||
|             btnDialogCancel.setOnClickListener(config.getOnClickCancel() != null ? config.getOnClickCancel() : getOnClickDismiss()); | ||||
|             btnDialogCancel.setVisibility(config.isHideCancel() ? View.GONE : View.VISIBLE); | ||||
| 
 | ||||
|             try{ | ||||
|                 //不强制要求要有横线
 | ||||
|                 View line = rootView.findViewById(R.id.line); | ||||
|                 line.setVisibility(config.isHideCancel() ? View.GONE : View.VISIBLE); | ||||
|             }catch (Exception e){ | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|             Button btnDialogOK = rootView.findViewById(config.getOkId()); | ||||
|             setText(btnDialogOK,config.getOk()); | ||||
|             btnDialogOK.setOnClickListener(config.getOnClickOk() != null ? config.getOnClickOk() : getOnClickDismiss()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,59 @@ | ||||
| package com.king.app.dialog.fragment; | ||||
| 
 | ||||
| import android.graphics.Color; | ||||
| import android.graphics.drawable.ColorDrawable; | ||||
| import android.os.Bundle; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.support.v4.app.DialogFragment; | ||||
| import android.text.TextUtils; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.view.Window; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
| /** | ||||
|  * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> | ||||
|  */ | ||||
| public abstract class BaseDialogFragment extends DialogFragment { | ||||
| 
 | ||||
|     private View mRootView; | ||||
| 
 | ||||
|     @Nullable | ||||
|     @Override | ||||
|     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { | ||||
|         super.getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE); | ||||
|         super.getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); | ||||
| 
 | ||||
|         mRootView = inflater.inflate(getRootLayoutId(), container, false); | ||||
|         init(mRootView); | ||||
|         return mRootView; | ||||
|     } | ||||
| 
 | ||||
|     protected View getRootView(){ | ||||
|         return mRootView; | ||||
|     } | ||||
| 
 | ||||
|     protected void setText(TextView tv, CharSequence text){ | ||||
|         if(!TextUtils.isEmpty(text)){ | ||||
|             tv.setText(text); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     protected View.OnClickListener getOnClickDismiss(){ | ||||
|         return mOnClickDismissDialog; | ||||
|     } | ||||
| 
 | ||||
|     private View.OnClickListener mOnClickDismissDialog = new View.OnClickListener() { | ||||
|         @Override | ||||
|         public void onClick(View v) { | ||||
|             dismiss(); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     public abstract int getRootLayoutId(); | ||||
| 
 | ||||
|     public abstract void init(View rootView); | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <set xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <scale android:duration="200" | ||||
|         android:pivotX="50%" | ||||
|         android:pivotY="50%" | ||||
|         android:fromXScale="0.0" | ||||
|         android:fromYScale="0.0" | ||||
|         android:toXScale="1.0" | ||||
|         android:toYScale="1.0"/> | ||||
| </set> | ||||
| @ -0,0 +1,10 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <set xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <scale android:duration="200" | ||||
|         android:pivotX="50%" | ||||
|         android:pivotY="50%" | ||||
|         android:fromXScale="1.0" | ||||
|         android:fromYScale="1.0" | ||||
|         android:toXScale="0.0" | ||||
|         android:toYScale="0.0"/> | ||||
| </set> | ||||
| @ -0,0 +1,5 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <selector xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <item android:state_pressed="true" android:color="@color/app_dialog_button_pressed_color"/> | ||||
|     <item android:state_pressed="false" android:color="@color/app_dialog_button_normal_color"/> | ||||
| </selector> | ||||
| @ -0,0 +1,12 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <shape xmlns:android="http://schemas.android.com/apk/res/android" > | ||||
| 
 | ||||
|     <solid android:color="@android:color/white" /> | ||||
| 
 | ||||
|     <corners | ||||
|         android:bottomLeftRadius="10dp" | ||||
|         android:bottomRightRadius="10dp" | ||||
|         android:topLeftRadius="10dp" | ||||
|         android:topRightRadius="10dp" /> | ||||
| 
 | ||||
| </shape> | ||||
| @ -0,0 +1,60 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="wrap_content" | ||||
|     android:layout_gravity="center" | ||||
|     android:layout_margin="30dp" | ||||
|     android:background="@drawable/app_dialog_bg" | ||||
|     android:orientation="vertical"> | ||||
|     <TextView | ||||
|         android:id="@+id/tvDialogTitle" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:gravity="center" | ||||
|         android:minHeight="40dp" | ||||
|         android:padding="6dp" | ||||
|         android:lines="1" | ||||
|         android:textSize="16sp" | ||||
|         android:textColor="@color/app_dialog_title_color" | ||||
|         android:text="@string/app_dialog_title"/> | ||||
|     <TextView | ||||
|         android:id="@+id/tvDialogContent" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:padding="10dp" | ||||
|         android:layout_marginBottom="6dp" | ||||
|         android:lineSpacingMultiplier="1" /> | ||||
|     <include layout="@layout/app_dialog_line_h"/> | ||||
|     <LinearLayout | ||||
|         android:orientation="horizontal" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:gravity="center_horizontal"> | ||||
|         <Button | ||||
|             android:id="@+id/btnDialogCancel" | ||||
|             android:layout_width="0dp" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:paddingTop="10dp" | ||||
|             android:paddingBottom="10dp" | ||||
|             android:layout_weight="1" | ||||
|             android:text="@string/app_dialog_cancel" | ||||
|             android:textSize="18sp" | ||||
|             android:textColor="@color/app_dialog_button_color_selector" | ||||
|             android:background="?android:attr/selectableItemBackground"/> | ||||
|         <include | ||||
|             android:id="@+id/line" | ||||
|             layout="@layout/app_dialog_line_v"/> | ||||
|         <Button | ||||
|             android:id="@+id/btnDialogOK" | ||||
|             android:layout_width="0dp" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:paddingTop="10dp" | ||||
|             android:paddingBottom="10dp" | ||||
|             android:layout_weight="1" | ||||
|             android:text="@string/app_dialog_ok" | ||||
|             android:textSize="18sp" | ||||
|             android:textColor="@color/app_dialog_button_color_selector" | ||||
|             android:background="?android:attr/selectableItemBackground"/> | ||||
|     </LinearLayout> | ||||
| 
 | ||||
| </LinearLayout> | ||||
| @ -0,0 +1,5 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <View xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="@dimen/app_dialog_line" | ||||
|     android:background="@color/app_dialog_line_color"/> | ||||
| @ -0,0 +1,5 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <View xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:layout_width="@dimen/app_dialog_line" | ||||
|     android:layout_height="match_parent" | ||||
|     android:background="@color/app_dialog_line_color"/> | ||||
| @ -0,0 +1,10 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
| 
 | ||||
|     <color name="app_dialog_title_color">#333333</color> | ||||
|     <color name="app_dialog_content_color">#666666</color> | ||||
|     <color name="app_dialog_button_normal_color">#333333</color> | ||||
|     <color name="app_dialog_button_pressed_color">#FF4081</color> | ||||
| 
 | ||||
|     <color name="app_dialog_line_color">#d2d2d2</color> | ||||
| </resources> | ||||
| @ -0,0 +1,5 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
| 
 | ||||
|     <dimen name="app_dialog_line">0.4dp</dimen> | ||||
| </resources> | ||||
| @ -0,0 +1,7 @@ | ||||
| <resources> | ||||
|     <string name="app_name">AppDialog</string> | ||||
| 
 | ||||
|     <string name="app_dialog_title">提示</string> | ||||
|     <string name="app_dialog_cancel">取消</string> | ||||
|     <string name="app_dialog_ok">确认</string> | ||||
| </resources> | ||||
| @ -0,0 +1,21 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
| 
 | ||||
|     <!-- dialog animation --> | ||||
|     <style name="app_dialog_animation" parent="android:style/Animation.Dialog"> | ||||
|         <item name="android:windowEnterAnimation">@anim/app_dialog_in</item> | ||||
|         <item name="android:windowExitAnimation">@anim/app_dialog_out</item> | ||||
|     </style> | ||||
| 
 | ||||
|     <!-- dialog style --> | ||||
|     <style name="app_dialog" parent="android:style/Theme.Dialog"> | ||||
|         <item name="android:windowFrame">@null</item> | ||||
|         <item name="android:windowIsFloating">true</item> | ||||
|         <item name="android:windowIsTranslucent">true</item> | ||||
|         <item name="android:windowNoTitle">true</item> | ||||
|         <item name="android:windowBackground">@android:color/transparent</item> | ||||
|         <item name="android:backgroundDimEnabled">true</item> | ||||
|         <item name="android:windowAnimationStyle">@style/app_dialog_animation</item> | ||||
|     </style> | ||||
| 
 | ||||
| </resources> | ||||
| @ -0,0 +1,17 @@ | ||||
| package com.king.app.dialog; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| import static org.junit.Assert.*; | ||||
| 
 | ||||
| /** | ||||
|  * Example local unit test, which will execute on the development machine (host). | ||||
|  * | ||||
|  * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> | ||||
|  */ | ||||
| public class ExampleUnitTest { | ||||
|     @Test | ||||
|     public void addition_isCorrect() { | ||||
|         assertEquals(4, 2 + 2); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1 @@ | ||||
| /build | ||||
| @ -0,0 +1,11 @@ | ||||
| apply plugin: 'com.novoda.bintray-release' | ||||
| 
 | ||||
| //添加 | ||||
| publish { | ||||
|     userOrg = 'jenly'//bintray.com用户名 | ||||
|     groupId = 'com.king.app'//jcenter上的路径 | ||||
|     artifactId = 'app-updater'//项目名称 | ||||
|     publishVersion = '1.0'//版本号 | ||||
|     desc = 'AppUpdater for Android'//描述 | ||||
|     website = 'https://github.com/jenly1314/AppUpdater'//网站 | ||||
| } | ||||
| @ -0,0 +1,35 @@ | ||||
| apply plugin: 'com.android.library' | ||||
| apply from: 'bintray.gradle' | ||||
| 
 | ||||
| android { | ||||
|     compileSdkVersion 27 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     defaultConfig { | ||||
|         minSdkVersion 15 | ||||
|         targetSdkVersion 27 | ||||
|         versionCode 1 | ||||
|         versionName "1.0" | ||||
| 
 | ||||
|         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     buildTypes { | ||||
|         release { | ||||
|             minifyEnabled false | ||||
|             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| dependencies { | ||||
|     implementation fileTree(dir: 'libs', include: ['*.jar']) | ||||
| 
 | ||||
|     compileOnly 'com.android.support:appcompat-v7:27.1.1' | ||||
|     testImplementation 'junit:junit:4.12' | ||||
|     androidTestImplementation 'com.android.support.test:runner:1.0.2' | ||||
|     androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' | ||||
| } | ||||
| @ -0,0 +1,21 @@ | ||||
| # Add project specific ProGuard rules here. | ||||
| # You can control the set of applied configuration files using the | ||||
| # proguardFiles setting in build.gradle. | ||||
| # | ||||
| # For more details, see | ||||
| #   http://developer.android.com/guide/developing/tools/proguard.html | ||||
| 
 | ||||
| # If your project uses WebView with JS, uncomment the following | ||||
| # and specify the fully qualified class name to the JavaScript interface | ||||
| # class: | ||||
| #-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||||
| #   public *; | ||||
| #} | ||||
| 
 | ||||
| # Uncomment this to preserve the line number information for | ||||
| # debugging stack traces. | ||||
| #-keepattributes SourceFile,LineNumberTable | ||||
| 
 | ||||
| # If you keep the line number information, uncomment this to | ||||
| # hide the original source file name. | ||||
| #-renamesourcefileattribute SourceFile | ||||
| @ -0,0 +1,26 @@ | ||||
| package com.king.app.updater; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.support.test.InstrumentationRegistry; | ||||
| import android.support.test.runner.AndroidJUnit4; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.*; | ||||
| 
 | ||||
| /** | ||||
|  * Instrumented test, which will execute on an Android device. | ||||
|  * | ||||
|  * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> | ||||
|  */ | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| public class ExampleInstrumentedTest { | ||||
|     @Test | ||||
|     public void useAppContext() { | ||||
|         // Context of the app under test.
 | ||||
|         Context appContext = InstrumentationRegistry.getTargetContext(); | ||||
| 
 | ||||
|         assertEquals("com.king.app.updater.test", appContext.getPackageName()); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,27 @@ | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     package="com.king.app.updater"> | ||||
| 
 | ||||
|     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | ||||
|     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> | ||||
| 
 | ||||
|     <uses-permission android:name="android.permission.READ_PHONE_STATE" /> | ||||
| 
 | ||||
|     <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> | ||||
|      | ||||
|     <application> | ||||
| 
 | ||||
|         <service android:name=".service.DownloadService"/> | ||||
| 
 | ||||
|         <provider | ||||
|             android:name=".provider.AppUpdaterFileProvider" | ||||
|             android:authorities="${applicationId}.fileProvider" | ||||
|             android:exported="false" | ||||
|             android:grantUriPermissions="true"> | ||||
|             <meta-data | ||||
|                 android:name="android.support.FILE_PROVIDER_PATHS" | ||||
|                 android:resource="@xml/app_updater_paths"/> | ||||
|         </provider> | ||||
| 
 | ||||
|     </application> | ||||
| 
 | ||||
| </manifest> | ||||
| @ -0,0 +1,239 @@ | ||||
| package com.king.app.updater; | ||||
| 
 | ||||
| import android.app.Activity; | ||||
| import android.content.ComponentName; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.ServiceConnection; | ||||
| import android.os.IBinder; | ||||
| import android.support.annotation.DrawableRes; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.text.TextUtils; | ||||
| 
 | ||||
| import com.king.app.updater.callback.UpdateCallback; | ||||
| import com.king.app.updater.constant.Constants; | ||||
| import com.king.app.updater.http.IHttpManager; | ||||
| import com.king.app.updater.service.DownloadService; | ||||
| import com.king.app.updater.util.PermissionUtils; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> | ||||
|  */ | ||||
| public class AppUpdater { | ||||
| 
 | ||||
|     private Context mContext; | ||||
| 
 | ||||
|     private UpdateConfig mConfig; | ||||
| 
 | ||||
|     private UpdateCallback mCallback; | ||||
| 
 | ||||
|     private IHttpManager mHttpManager; | ||||
| 
 | ||||
|     private ServiceConnection mServiceConnection; | ||||
| 
 | ||||
| 
 | ||||
|     public AppUpdater(@NonNull Context context,@NonNull UpdateConfig config){ | ||||
|         this.mContext = context; | ||||
|         this.mConfig = config; | ||||
|     } | ||||
| 
 | ||||
|     public AppUpdater(@NonNull Context context,@NonNull String url){ | ||||
|         this.mContext = context; | ||||
|         mConfig = new UpdateConfig(); | ||||
|         mConfig.setUrl(url); | ||||
|     } | ||||
| 
 | ||||
|     public AppUpdater setUpdateCallback(UpdateCallback callback){ | ||||
|         this.mCallback = callback; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public AppUpdater setHttpManager(IHttpManager httpManager){ | ||||
|         this.mHttpManager = httpManager; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 开始下载 | ||||
|      */ | ||||
|     public void start(){ | ||||
|         if(mConfig!=null && !TextUtils.isEmpty(mConfig.getUrl())){ | ||||
|             //mContext不强制要求是Activity,能传Activity尽量传。AppUpdater本应该只专注于App更新,尽量不涉及动态权限相关的处理。如果mContext不强制要求是Activity是Activity默认会优先校验一次动态权限。
 | ||||
|             if(mContext instanceof Activity){ | ||||
|                 PermissionUtils.INSTANCE.verifyReadAndWritePermissions((Activity) mContext,Constants.RE_CODE_STORAGE_PERMISSION); | ||||
|             } | ||||
|             startDownloadService(); | ||||
|         }else{ | ||||
|             throw new NullPointerException("url = null"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 启动下载服务 | ||||
|      */ | ||||
|     private void startDownloadService(){ | ||||
| 
 | ||||
|         Intent intent = new Intent(mContext, DownloadService.class); | ||||
|         if(mCallback!=null || mHttpManager!=null){//bindService
 | ||||
|             mServiceConnection = new ServiceConnection() { | ||||
|                 @Override | ||||
|                 public void onServiceConnected(ComponentName name, IBinder service) { | ||||
|                     DownloadService.DownloadBinder binder = ((DownloadService.DownloadBinder)service); | ||||
|                     binder.start(mConfig,mHttpManager,mCallback); | ||||
|                 } | ||||
| 
 | ||||
|                 @Override | ||||
|                 public void onServiceDisconnected(ComponentName name) { | ||||
| 
 | ||||
|                 } | ||||
|             }; | ||||
| 
 | ||||
|             mContext.getApplicationContext().bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE); | ||||
|         }else{//startService
 | ||||
|             intent.putExtra(Constants.KEY_UPDATE_CONFIG,mConfig); | ||||
|             mContext.startService(intent); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * AppUpdater构建器 | ||||
|      */ | ||||
|     public static class Builder{ | ||||
| 
 | ||||
|         private UpdateConfig mConfig; | ||||
| 
 | ||||
|         public Builder(){ | ||||
|             mConfig = new UpdateConfig(); | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * | ||||
|          * @param url 下载地址 | ||||
|          * @return | ||||
|          */ | ||||
|         public Builder serUrl(@NonNull String url){ | ||||
|             mConfig.setUrl(url); | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * | ||||
|          * @param path  下载保存的文件路径 (默认SD卡/.AppUpdater目录) | ||||
|          * @return | ||||
|          */ | ||||
|         public Builder setPath(String path){ | ||||
|             mConfig.setPath(path); | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * | ||||
|          * @param filename 下载的保存的apk文件名 (默认优先取url文件名) | ||||
|          * @return | ||||
|          */ | ||||
|         public Builder setFilename(String filename){ | ||||
|             mConfig.setFilename(filename); | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * | ||||
|          * @param isShowNotification 是否显示通知栏 (默认true) | ||||
|          * @return | ||||
|          */ | ||||
|         public Builder setShowNotification(boolean isShowNotification) { | ||||
|             mConfig.setShowNotification(isShowNotification); | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * | ||||
|          * @param notifyId 通知ID | ||||
|          * @return | ||||
|          */ | ||||
|         public Builder setNotificationId(int notifyId) { | ||||
|             mConfig.setNotificationId(notifyId); | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * | ||||
|          * @param channelId 通知渠道ID (默认兼容O) | ||||
|          * @return | ||||
|          */ | ||||
|         public Builder setChannelId(String channelId) { | ||||
|             mConfig.setChannelId(channelId); | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * | ||||
|          * @param channelName 通知渠道名称 (默认兼容O) | ||||
|          * @return | ||||
|          */ | ||||
|         public Builder setChannelName(String channelName) { | ||||
|             mConfig.setChannelName(channelName); | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * | ||||
|          * @param icon 通知栏图标 (默认取App的icon) | ||||
|          * @return | ||||
|          */ | ||||
|         public Builder setNotificationIcon(@DrawableRes int icon) { | ||||
|             mConfig.setNotificationIcon(icon); | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * | ||||
|          * @param isInstallApk 下载完成后是否自动调用安装APK(默认true) | ||||
|          * @return | ||||
|          */ | ||||
|         public Builder setInstallApk(boolean isInstallApk){ | ||||
|             mConfig.setInstallApk(isInstallApk); | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * | ||||
|          * @param authority FileProvider的authority(默认兼容N,默认值context.getPackageName() + ".fileProvider") | ||||
|          * @return | ||||
|          */ | ||||
|         public Builder setAuthority(String authority){ | ||||
|             mConfig.setAuthority(authority); | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * | ||||
|          * @param showPercentage 下载时通知栏是否显示百分比 | ||||
|          * @return | ||||
|          */ | ||||
|         public Builder setShowPercentage(boolean showPercentage) { | ||||
|             mConfig.setShowPercentage(showPercentage); | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * | ||||
|          * @param reDownload 下载失败时是否支持点击通知栏重新下载,默认true 最多重新下载3次 | ||||
|          * @return | ||||
|          */ | ||||
|         public Builder setReDownload(boolean reDownload) { | ||||
|             mConfig.setReDownload(reDownload); | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public AppUpdater build(@NonNull Context context){ | ||||
|             AppUpdater appUpdater = new AppUpdater(context,mConfig); | ||||
|             return appUpdater; | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,218 @@ | ||||
| package com.king.app.updater; | ||||
| 
 | ||||
| 
 | ||||
| import android.os.Parcel; | ||||
| import android.os.Parcelable; | ||||
| import android.support.annotation.DrawableRes; | ||||
| 
 | ||||
| import com.king.app.updater.constant.Constants; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> | ||||
|  */ | ||||
| public class UpdateConfig implements Parcelable { | ||||
| 
 | ||||
| 
 | ||||
|     private String mUrl; | ||||
|     /** | ||||
|      * 保存路径 | ||||
|      */ | ||||
|     private String mPath; | ||||
|     /** | ||||
|      * 保存文件名 | ||||
|      */ | ||||
|     private String mFilename; | ||||
| 
 | ||||
|     private boolean isShowNotification = true; | ||||
|     /** | ||||
|      * 下载完成后是否自动弹出安装 | ||||
|      */ | ||||
|     private boolean isInstallApk = true; | ||||
| 
 | ||||
|     private int mNotificationIcon; | ||||
| 
 | ||||
|     private int mNotificationId = Constants.DEFAULT_NOTIFICATION_ID; | ||||
| 
 | ||||
|     private String mChannelId; | ||||
| 
 | ||||
|     private String mChannelName; | ||||
| 
 | ||||
|     private String mAuthority; | ||||
|     /** | ||||
|      * 下载失败是否支持点击通知栏重复下载 | ||||
|      */ | ||||
|     private boolean isReDownload = true; | ||||
|     /** | ||||
|      * 是否显示百分比 | ||||
|      */ | ||||
|     private boolean isShowPercentage = true; | ||||
| 
 | ||||
| 
 | ||||
|     public UpdateConfig() { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public String getUrl() { | ||||
|         return mUrl; | ||||
|     } | ||||
| 
 | ||||
|     public void setUrl(String url) { | ||||
|         this.mUrl = url; | ||||
|     } | ||||
| 
 | ||||
|     public String getPath() { | ||||
|         return mPath; | ||||
|     } | ||||
| 
 | ||||
|     public void setPath(String path) { | ||||
|         this.mPath = path; | ||||
|     } | ||||
| 
 | ||||
|     public String getFilename() { | ||||
|         return mFilename; | ||||
|     } | ||||
| 
 | ||||
|     public void setFilename(String filename) { | ||||
|         this.mFilename = filename; | ||||
|     } | ||||
| 
 | ||||
|     public boolean isShowNotification() { | ||||
|         return isShowNotification; | ||||
|     } | ||||
| 
 | ||||
|     public void setShowNotification(boolean isShowNotification) { | ||||
|         this.isShowNotification = isShowNotification; | ||||
|     } | ||||
| 
 | ||||
|     public String getChannelId() { | ||||
|         return mChannelId; | ||||
|     } | ||||
| 
 | ||||
|     public void setChannelId(String channelId) { | ||||
|         this.mChannelId = channelId; | ||||
|     } | ||||
| 
 | ||||
|     public String getChannelName() { | ||||
|         return mChannelName; | ||||
|     } | ||||
| 
 | ||||
|     public void setChannelName(String channelName) { | ||||
|         this.mChannelName = channelName; | ||||
|     } | ||||
| 
 | ||||
|     public void setNotificationId(int notificationId){ | ||||
|         this.mNotificationId = notificationId; | ||||
|     } | ||||
| 
 | ||||
|     public int getNotificationId(){ | ||||
|         return this.mNotificationId; | ||||
|     } | ||||
| 
 | ||||
|     public void setNotificationIcon(@DrawableRes int icon){ | ||||
|         this.mNotificationIcon = icon; | ||||
|     } | ||||
| 
 | ||||
|     public int getNotificationIcon(){ | ||||
|         return this.mNotificationIcon; | ||||
|     } | ||||
| 
 | ||||
|     public boolean isInstallApk() { | ||||
|         return isInstallApk; | ||||
|     } | ||||
| 
 | ||||
|     public void setInstallApk(boolean isInstallApk) { | ||||
|         this.isInstallApk = isInstallApk; | ||||
|     } | ||||
| 
 | ||||
|     public String getAuthority() { | ||||
|         return mAuthority; | ||||
|     } | ||||
| 
 | ||||
|     public void setAuthority(String authority) { | ||||
|         this.mAuthority = authority; | ||||
|     } | ||||
| 
 | ||||
|     public boolean isShowPercentage() { | ||||
|         return isShowPercentage; | ||||
|     } | ||||
| 
 | ||||
|     public void setShowPercentage(boolean showPercentage) { | ||||
|         isShowPercentage = showPercentage; | ||||
|     } | ||||
| 
 | ||||
|     public boolean isReDownload() { | ||||
|         return isReDownload; | ||||
|     } | ||||
| 
 | ||||
|     public void setReDownload(boolean reDownload) { | ||||
|         isReDownload = reDownload; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         return "UpdateConfig{" + | ||||
|                 "mUrl='" + mUrl + '\'' + | ||||
|                 ", mPath='" + mPath + '\'' + | ||||
|                 ", mFilename='" + mFilename + '\'' + | ||||
|                 ", isShowNotification=" + isShowNotification + | ||||
|                 ", isInstallApk=" + isInstallApk + | ||||
|                 ", mNotificationIcon=" + mNotificationIcon + | ||||
|                 ", mNotificationId=" + mNotificationId + | ||||
|                 ", mChannelId='" + mChannelId + '\'' + | ||||
|                 ", mChannelName='" + mChannelName + '\'' + | ||||
|                 ", mAuthority='" + mAuthority + '\'' + | ||||
|                 ", isShowPercentage=" + isShowPercentage + | ||||
|                 ", isReDownload=" + isReDownload + | ||||
|                 '}'; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int describeContents() { | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void writeToParcel(Parcel dest, int flags) { | ||||
|         dest.writeString(this.mUrl); | ||||
|         dest.writeString(this.mPath); | ||||
|         dest.writeString(this.mFilename); | ||||
|         dest.writeByte(this.isShowNotification ? (byte) 1 : (byte) 0); | ||||
|         dest.writeByte(this.isInstallApk ? (byte) 1 : (byte) 0); | ||||
|         dest.writeInt(this.mNotificationIcon); | ||||
|         dest.writeInt(this.mNotificationId); | ||||
|         dest.writeString(this.mChannelId); | ||||
|         dest.writeString(this.mChannelName); | ||||
|         dest.writeString(this.mAuthority); | ||||
|         dest.writeByte(this.isReDownload ? (byte) 1 : (byte) 0); | ||||
|         dest.writeByte(this.isShowPercentage ? (byte) 1 : (byte) 0); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     protected UpdateConfig(Parcel in) { | ||||
|         this.mUrl = in.readString(); | ||||
|         this.mPath = in.readString(); | ||||
|         this.mFilename = in.readString(); | ||||
|         this.isShowNotification = in.readByte() != 0; | ||||
|         this.isInstallApk = in.readByte() != 0; | ||||
|         this.mNotificationIcon = in.readInt(); | ||||
|         this.mNotificationId = in.readInt(); | ||||
|         this.mChannelId = in.readString(); | ||||
|         this.mChannelName = in.readString(); | ||||
|         this.mAuthority = in.readString(); | ||||
|         this.isReDownload = in.readByte() != 0; | ||||
|         this.isShowPercentage = in.readByte() != 0; | ||||
|     } | ||||
| 
 | ||||
|     public static final Creator<UpdateConfig> CREATOR = new Creator<UpdateConfig>() { | ||||
|         @Override | ||||
|         public UpdateConfig createFromParcel(Parcel source) { | ||||
|             return new UpdateConfig(source); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public UpdateConfig[] newArray(int size) { | ||||
|             return new UpdateConfig[size]; | ||||
|         } | ||||
|     }; | ||||
| } | ||||
| @ -0,0 +1,26 @@ | ||||
| package com.king.app.updater.callback; | ||||
| 
 | ||||
| /** | ||||
|  * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> | ||||
|  */ | ||||
| public abstract class AppUpdateCallback implements UpdateCallback { | ||||
|     @Override | ||||
|     public void onDownloading(boolean isDownloading) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onStart(String url) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onError(Exception e) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCancel() { | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,46 @@ | ||||
| package com.king.app.updater.callback; | ||||
| 
 | ||||
| import java.io.File; | ||||
| 
 | ||||
| /** | ||||
|  * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> | ||||
|  */ | ||||
| public interface UpdateCallback { | ||||
| 
 | ||||
|     /** | ||||
|      * 最开始调用(在onStart之前调用) | ||||
|      * @param isDownloading true 表示已经在下载,false表示准备刚调用下载 | ||||
|      */ | ||||
|     void onDownloading(boolean isDownloading); | ||||
| 
 | ||||
|     /** | ||||
|      * 开始 | ||||
|      */ | ||||
|     void onStart(String url); | ||||
| 
 | ||||
|     /** | ||||
|      * 加载进度… | ||||
|      * @param progress | ||||
|      * @param total | ||||
|      * @param isChange 进度百分比是否有改变,(主要可以用来过滤无用的刷新,从而降低刷新频率) | ||||
|      */ | ||||
|     void onProgress(int progress,int total,boolean isChange); | ||||
| 
 | ||||
|     /** | ||||
|      * 完成 | ||||
|      * @param file | ||||
|      */ | ||||
|     void onFinish(File file); | ||||
| 
 | ||||
|     /** | ||||
|      * 错误 | ||||
|      * @param e | ||||
|      */ | ||||
|     void onError(Exception e); | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * 取消 | ||||
|      */ | ||||
|     void onCancel(); | ||||
| } | ||||
| @ -0,0 +1,31 @@ | ||||
| package com.king.app.updater.constant; | ||||
| 
 | ||||
| import android.os.Environment; | ||||
| 
 | ||||
| import java.io.File; | ||||
| 
 | ||||
| /** | ||||
|  * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> | ||||
|  */ | ||||
| public final class Constants { | ||||
| 
 | ||||
|     public static final String TAG = "AppUpdater"; | ||||
| 
 | ||||
|     public static final String KEY_UPDATE_CONFIG = "app_update_config"; | ||||
| 
 | ||||
|     public static final int DEFAULT_NOTIFICATION_ID = 0x66; | ||||
| 
 | ||||
|     public static final String DEFAULT_NOTIFICATION_CHANNEL_ID = "0x66"; | ||||
| 
 | ||||
|     public static final String DEFAULT_NOTIFICATION_CHANNEL_NAME = "AppUpdater"; | ||||
| 
 | ||||
|     public static final String KEY_STOP_DOWNLOAD_SERVICE = "stop_download_service"; | ||||
| 
 | ||||
|     public static final String KEY_RE_DOWNLOAD = "app_update_re_download"; | ||||
| 
 | ||||
|     public static final String DEFAULT_DIR_PATH = Environment.getExternalStorageDirectory() + File.separator + ".AppUpdater"; | ||||
| 
 | ||||
|     public static final int RE_CODE_STORAGE_PERMISSION = 0x66; | ||||
| 
 | ||||
|     public static final int NONE = -1; | ||||
| } | ||||
| @ -0,0 +1,166 @@ | ||||
| package com.king.app.updater.http; | ||||
| 
 | ||||
| import android.os.AsyncTask; | ||||
| 
 | ||||
| import com.king.app.updater.util.SSLSocketFactoryUtils; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.InputStream; | ||||
| import java.net.ConnectException; | ||||
| import java.net.HttpURLConnection; | ||||
| import java.net.URL; | ||||
| 
 | ||||
| import javax.net.ssl.HttpsURLConnection; | ||||
| 
 | ||||
| /** | ||||
|  * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> | ||||
|  */ | ||||
| public class HttpManager implements IHttpManager { | ||||
| 
 | ||||
|     private static final int DEFAULT_TIME_OUT = 20000; | ||||
| 
 | ||||
|     private int mTimeout = DEFAULT_TIME_OUT; | ||||
| 
 | ||||
|     private static HttpManager INSTANCE; | ||||
| 
 | ||||
|     public static HttpManager getInstance(){ | ||||
|         if(INSTANCE == null){ | ||||
|             synchronized (HttpManager.class){ | ||||
|                 INSTANCE = new HttpManager(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return INSTANCE; | ||||
|     } | ||||
| 
 | ||||
|     private HttpManager(){ | ||||
|         this(DEFAULT_TIME_OUT); | ||||
|     } | ||||
| 
 | ||||
|     public HttpManager(int timeout){ | ||||
|         this.mTimeout = timeout; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void download(String url, String path, String filename, DownloadCallback callback) { | ||||
|         new DownloadTask(url,path,filename,callback).execute(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * 异步下载任务 | ||||
|      */ | ||||
|     private class DownloadTask extends AsyncTask<Void,Integer,File> { | ||||
|         private String url; | ||||
| 
 | ||||
|         private String path; | ||||
| 
 | ||||
|         private String filename; | ||||
| 
 | ||||
|         private DownloadCallback callback; | ||||
| 
 | ||||
|         private Exception exception; | ||||
| 
 | ||||
|         public DownloadTask(String url,String path,String filename,DownloadCallback callback){ | ||||
|             this.url = url; | ||||
|             this.path = path; | ||||
|             this.filename = filename; | ||||
|             this.callback = callback; | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         protected File doInBackground(Void... voids) { | ||||
| 
 | ||||
|             try { | ||||
|                 HttpsURLConnection.setDefaultSSLSocketFactory(SSLSocketFactoryUtils.createSSLSocketFactory()); | ||||
|                 HttpsURLConnection.setDefaultHostnameVerifier(SSLSocketFactoryUtils.createTrustAllHostnameVerifier()); | ||||
|                 HttpURLConnection connect = (HttpURLConnection)new URL(url).openConnection(); | ||||
|                 connect.setRequestMethod("GET"); | ||||
|                 connect.setReadTimeout(mTimeout); | ||||
|                 connect.setConnectTimeout(mTimeout); | ||||
|                 connect.connect(); | ||||
|                 int responseCode = connect.getResponseCode(); | ||||
| 
 | ||||
|                 if(responseCode == HttpURLConnection.HTTP_OK){ | ||||
| 
 | ||||
|                     InputStream is = connect.getInputStream(); | ||||
| 
 | ||||
|                     int length = connect.getContentLength(); | ||||
| 
 | ||||
|                     int progress = 0; | ||||
| 
 | ||||
|                     byte[] buffer = new byte[4096]; | ||||
| 
 | ||||
|                     int len; | ||||
|                     File file = new File(path,filename); | ||||
|                     FileOutputStream fos = new FileOutputStream(file); | ||||
|                     while ((len = is.read(buffer)) != -1){ | ||||
|                         fos.write(buffer,0,len); | ||||
|                         progress += len; | ||||
|                         //更新进度
 | ||||
|                         publishProgress(progress,length); | ||||
|                     } | ||||
| 
 | ||||
|                     fos.flush(); | ||||
|                     fos.close(); | ||||
|                     is.close(); | ||||
| 
 | ||||
|                     connect.disconnect(); | ||||
| 
 | ||||
|                     return file; | ||||
|                 }else {//连接失败
 | ||||
|                     throw new ConnectException(String.format("responseCode = %d",responseCode)); | ||||
|                 } | ||||
| 
 | ||||
|             } catch (Exception e) { | ||||
|                 this.exception = e; | ||||
|                 e.printStackTrace(); | ||||
|             } | ||||
| 
 | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         protected void onPreExecute() { | ||||
|             super.onPreExecute(); | ||||
|             if(callback!=null){ | ||||
|                 callback.onStart(url); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         protected void onPostExecute(File file) { | ||||
|             super.onPostExecute(file); | ||||
|             if(callback!=null){ | ||||
|                 if(file!=null){ | ||||
|                     callback.onFinish(file); | ||||
|                 }else{ | ||||
|                     callback.onError(exception); | ||||
|                 } | ||||
| 
 | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         protected void onProgressUpdate(Integer... values) { | ||||
|             super.onProgressUpdate(values); | ||||
|             if(callback!=null){ | ||||
|                 callback.onProgress(values[0],values[1]); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         @Override | ||||
|         protected void onCancelled() { | ||||
|             super.onCancelled(); | ||||
|             if(callback!=null){ | ||||
|                 callback.onCancel(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,54 @@ | ||||
| package com.king.app.updater.http; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.Serializable; | ||||
| 
 | ||||
| /** | ||||
|  * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> | ||||
|  */ | ||||
| public interface IHttpManager { | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * 下载 | ||||
|      * @param url | ||||
|      * @param path | ||||
|      * @param filename | ||||
|      * @param callback | ||||
|      */ | ||||
|     void download(String url,String path,String filename,DownloadCallback callback); | ||||
| 
 | ||||
| 
 | ||||
|     interface DownloadCallback extends Serializable{ | ||||
|         /** | ||||
|          * 开始 | ||||
|          * @param url | ||||
|          */ | ||||
|         void onStart(String url); | ||||
| 
 | ||||
|         /** | ||||
|          * 加载进度… | ||||
|          * @param progress | ||||
|          * @param total | ||||
|          */ | ||||
|         void onProgress(int progress,int total); | ||||
| 
 | ||||
|         /** | ||||
|          * 完成 | ||||
|          * @param file | ||||
|          */ | ||||
|         void onFinish(File file); | ||||
| 
 | ||||
|         /** | ||||
|          * 错误 | ||||
|          * @param e | ||||
|          */ | ||||
|         void onError(Exception e); | ||||
| 
 | ||||
| 
 | ||||
|         /** | ||||
|          * 取消 | ||||
|          */ | ||||
|         void onCancel(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| package com.king.app.updater.provider; | ||||
| 
 | ||||
| import android.support.v4.content.FileProvider; | ||||
| 
 | ||||
| /** | ||||
|  * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> | ||||
|  */ | ||||
| public class AppUpdaterFileProvider extends FileProvider { | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,527 @@ | ||||
| package com.king.app.updater.service; | ||||
| 
 | ||||
| import android.app.Notification; | ||||
| import android.app.NotificationChannel; | ||||
| import android.app.NotificationManager; | ||||
| import android.app.PendingIntent; | ||||
| import android.app.Service; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.os.Binder; | ||||
| import android.os.Build; | ||||
| import android.os.Environment; | ||||
| import android.os.IBinder; | ||||
| import android.support.annotation.DrawableRes; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.support.annotation.RequiresApi; | ||||
| import android.support.v4.app.NotificationCompat; | ||||
| import android.support.v4.content.FileProvider; | ||||
| import android.text.TextUtils; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import com.king.app.updater.R; | ||||
| import com.king.app.updater.UpdateConfig; | ||||
| import com.king.app.updater.callback.UpdateCallback; | ||||
| import com.king.app.updater.constant.Constants; | ||||
| import com.king.app.updater.http.HttpManager; | ||||
| import com.king.app.updater.http.IHttpManager; | ||||
| import com.king.app.updater.util.AppUtils; | ||||
| 
 | ||||
| import java.io.File; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> | ||||
|  */ | ||||
| public class DownloadService extends Service { | ||||
| 
 | ||||
|     private DownloadBinder mDownloadBinder = new DownloadBinder(); | ||||
|     /** | ||||
|      * 是否在下载,防止重复下载。 | ||||
|      */ | ||||
|     private boolean isDownloading; | ||||
|     /** | ||||
|      * 最后更新进度,用来降频刷新 | ||||
|      */ | ||||
|     private int mLastProgress = -1; | ||||
|     /** | ||||
|      * 最后进度更新时间,用来降频刷新 | ||||
|      */ | ||||
|     private long mLastTime; | ||||
|     /** | ||||
|      * 失败后重新下载次数 | ||||
|      */ | ||||
|     private int mCount = 0; | ||||
| 
 | ||||
|     private Context getContext(){ | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int onStartCommand(Intent intent, int flags, int startId) { | ||||
| 
 | ||||
|         if(intent != null){ | ||||
|             if(!isDownloading){ | ||||
|                 boolean isStop = intent.getBooleanExtra(Constants.KEY_STOP_DOWNLOAD_SERVICE,false); | ||||
|                 if(isStop){ | ||||
|                     stopService(); | ||||
|                 }else{ | ||||
|                     //是否实通过通知栏触发重复下载
 | ||||
|                     boolean isReDownload = intent.getBooleanExtra(Constants.KEY_RE_DOWNLOAD,false); | ||||
|                     if(isReDownload){ | ||||
|                         mCount++; | ||||
|                     } | ||||
|                     //获取配置信息
 | ||||
|                     UpdateConfig config =  intent.getParcelableExtra(Constants.KEY_UPDATE_CONFIG); | ||||
|                     startDownload(config,null,null); | ||||
|                 } | ||||
|             }else{ | ||||
|                 Log.w(Constants.TAG,"已经在下载中,请勿重复下载。"); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return super.onStartCommand(intent, flags, startId); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     //----------------------------------------
 | ||||
| 
 | ||||
|     /** | ||||
|      * 开始下载 | ||||
|      * @param config | ||||
|      * @param httpManager | ||||
|      * @param callback | ||||
|      */ | ||||
|     public void startDownload(UpdateConfig config,IHttpManager httpManager,UpdateCallback callback){ | ||||
| 
 | ||||
|         if(config == null){ | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if(callback!=null){ | ||||
|             callback.onDownloading(isDownloading); | ||||
|         } | ||||
| 
 | ||||
|         if(isDownloading){ | ||||
|             Log.w(Constants.TAG,"已经在下载中,请勿重复下载。"); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         String url = config.getUrl(); | ||||
|         String path = config.getPath(); | ||||
|         String filename = config.getFilename(); | ||||
| 
 | ||||
|         //如果保存路径为空则使用缓存路径
 | ||||
|         if(TextUtils.isEmpty(path)){ | ||||
|             path = getDiskCacheDir(getContext()); | ||||
|         } | ||||
|         File dirFile = new File(path); | ||||
|         if(!dirFile.exists()){ | ||||
|             dirFile.mkdir(); | ||||
|         } | ||||
| 
 | ||||
|         //如果文件名为空则使用路径
 | ||||
|         if(TextUtils.isEmpty(filename)){ | ||||
|             filename = AppUtils.INSTANCE.getAppFullName(getContext(),url,getResources().getString(R.string.app_name)); | ||||
|         } | ||||
| 
 | ||||
|         File file = new File(path,filename); | ||||
|         if(file.exists()){ | ||||
|             file.delete(); | ||||
|         } | ||||
| 
 | ||||
|         if(httpManager != null){ | ||||
|             httpManager.download(url,path,filename,new AppDownloadCallback(config,callback)); | ||||
|         }else{ | ||||
|             HttpManager.getInstance().download(url,path,filename,new AppDownloadCallback(config,callback)); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取缓存路径 | ||||
|      * @param context | ||||
|      * @return | ||||
|      */ | ||||
|     public String getDiskCacheDir(Context context) { | ||||
|         if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { | ||||
|             return Constants.DEFAULT_DIR_PATH; | ||||
|         } | ||||
| 
 | ||||
|         return context.getCacheDir().getAbsolutePath(); | ||||
|     } | ||||
| 
 | ||||
|     private void stopService(){ | ||||
|         mCount = 0; | ||||
|         stopSelf(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     //---------------------------------------- DownloadCallback
 | ||||
| 
 | ||||
|     public class AppDownloadCallback implements IHttpManager.DownloadCallback{ | ||||
| 
 | ||||
|         public UpdateConfig config; | ||||
| 
 | ||||
|         private boolean isShowNotification; | ||||
| 
 | ||||
|         private int notifyId; | ||||
| 
 | ||||
|         private String channelId; | ||||
| 
 | ||||
|         private String channelName; | ||||
| 
 | ||||
|         private int notificationIcon; | ||||
| 
 | ||||
|         private boolean isInstallApk; | ||||
| 
 | ||||
|         private String authority; | ||||
| 
 | ||||
|         private boolean isShowPercentage; | ||||
| 
 | ||||
|         private boolean isReDownload; | ||||
| 
 | ||||
|         private UpdateCallback callback; | ||||
| 
 | ||||
| 
 | ||||
|         private AppDownloadCallback(UpdateConfig config,UpdateCallback callback){ | ||||
|             this.config = config; | ||||
|             this.callback = callback; | ||||
|             this.isShowNotification = config.isShowNotification(); | ||||
|             this.notifyId = config.getNotificationId(); | ||||
| 
 | ||||
|             if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ | ||||
|                 this.channelId = TextUtils.isEmpty(config.getChannelId()) ? Constants.DEFAULT_NOTIFICATION_CHANNEL_ID : config.getChannelId(); | ||||
|                 this.channelName = TextUtils.isEmpty(config.getChannelName()) ? Constants.DEFAULT_NOTIFICATION_CHANNEL_NAME : config.getChannelName(); | ||||
|             } | ||||
|             if(config.getNotificationIcon()<=0){ | ||||
|                 this.notificationIcon = AppUtils.INSTANCE.getAppIcon(getContext()); | ||||
|             }else{ | ||||
|                 this.notificationIcon = config.getNotificationIcon(); | ||||
|             } | ||||
| 
 | ||||
|             this.isInstallApk = config.isInstallApk(); | ||||
|             if(TextUtils.isEmpty(config.getAuthority())){//如果为空默认
 | ||||
|                 authority = getContext().getPackageName() + ".fileProvider"; | ||||
|             }else{ | ||||
|                 this.authority = config.getAuthority(); | ||||
|             } | ||||
| 
 | ||||
|             this.isShowPercentage = config.isShowPercentage(); | ||||
|             this.isReDownload = config.isReDownload(); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         @Override | ||||
|         public void onStart(String url) { | ||||
|             isDownloading = true; | ||||
|             mLastProgress = 0; | ||||
|             if(isShowNotification){ | ||||
|                 showStartNotification(notifyId,channelId,channelName,notificationIcon,getString(R.string.app_updater_start_notification_title),getString(R.string.app_updater_start_notification_content)); | ||||
|             } | ||||
| 
 | ||||
|             if(callback!=null){ | ||||
|                 callback.onStart(url); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onProgress(int progress, int total) { | ||||
| 
 | ||||
|             boolean isChange = false; | ||||
|             long curTime = System.currentTimeMillis(); | ||||
|             if(mLastTime + 200 < curTime) {//降低更新频率
 | ||||
|                 mLastTime = curTime; | ||||
| 
 | ||||
|                 int currProgress = Math.round(progress * 1.0f / total * 100.0f); | ||||
|                 if(currProgress!=mLastProgress){//百分比改变了才更新
 | ||||
|                     isChange = true; | ||||
|                     String percentage = currProgress + "%"; | ||||
|                     if(isShowNotification) { | ||||
|                         mLastProgress = currProgress; | ||||
|                         String content = getString(R.string.app_updater_progress_notification_content); | ||||
|                         if (isShowPercentage) { | ||||
|                             content += percentage; | ||||
|                         } | ||||
| 
 | ||||
|                         showProgressNotification(notifyId, channelId, notificationIcon, getString(R.string.app_updater_progress_notification_title), content, progress, total); | ||||
| 
 | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if(callback!=null){ | ||||
|                 callback.onProgress(progress,total,isChange); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onFinish(File file) { | ||||
|             isDownloading = false; | ||||
|             showFinishNotification(notifyId,channelId,notificationIcon,getString(R.string.app_updater_finish_notification_title),getString(R.string.app_updater_finish_notification_content),file,authority); | ||||
|             if(isInstallApk){ | ||||
|                 AppUtils.INSTANCE.installApk(getContext(),file,authority); | ||||
|             } | ||||
|             if(callback!=null){ | ||||
|                 callback.onFinish(file); | ||||
|             } | ||||
|             stopService(); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onError(Exception e) { | ||||
|             isDownloading = false; | ||||
|             //支持下载失败重新并最多支持失败下载3次
 | ||||
|             boolean isReDownload = this.isReDownload && mCount < 3; | ||||
|             String content = isReDownload ? getString(R.string.app_updater_error_notification_content_re_download) : getString(R.string.app_updater_error_notification_content); | ||||
|             showErrorNotification(notifyId,channelId,notificationIcon,getString(R.string.app_updater_error_notification_title),content,isReDownload,config); | ||||
| 
 | ||||
|             if(callback!=null){ | ||||
|                 callback.onError(e); | ||||
|             } | ||||
|             if(!isReDownload){ | ||||
|                 stopService(); | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onCancel() { | ||||
|             isDownloading = false; | ||||
|             cancelNotification(notifyId); | ||||
|             if(callback!=null){ | ||||
|                 callback.onCancel(); | ||||
|             } | ||||
|             stopService(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean onUnbind(Intent intent) { | ||||
|         Log.d(Constants.TAG,"onUnbind"); | ||||
|         return super.onUnbind(intent); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onDestroy() { | ||||
|         isDownloading = false; | ||||
|         Log.d(Constants.TAG,"onDestroy"); | ||||
|         super.onDestroy(); | ||||
|     } | ||||
| 
 | ||||
|     //---------------------------------------- Notification
 | ||||
| 
 | ||||
|     /** | ||||
|      * 显示开始下载是的通知 | ||||
|      * @param notifyId | ||||
|      * @param channelId | ||||
|      * @param channelName | ||||
|      * @param icon | ||||
|      * @param title | ||||
|      * @param content | ||||
|      */ | ||||
|     private void showStartNotification(int notifyId,String channelId, String channelName,@DrawableRes int icon,CharSequence title,CharSequence content){ | ||||
|         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ | ||||
|             createNotificationChannel(channelId,channelName); | ||||
|         } | ||||
|         NotificationCompat.Builder builder = buildNotification(channelId,icon,title,content); | ||||
|         builder.setAutoCancel(false); | ||||
|         notifyNotification(notifyId,builder.build()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 显示下载中的通知(更新进度) | ||||
|      * @param notifyId | ||||
|      * @param channelId | ||||
|      * @param icon | ||||
|      * @param title | ||||
|      * @param content | ||||
|      * @param progress | ||||
|      * @param size | ||||
|      */ | ||||
|     private void showProgressNotification(int notifyId,String channelId,@DrawableRes int icon,CharSequence title,CharSequence content,int progress,int size){ | ||||
|         NotificationCompat.Builder builder = buildNotification(channelId,icon,title,content,progress,size); | ||||
|         builder.setAutoCancel(false); | ||||
|         Notification notification = builder.build(); | ||||
|         notification.flags = Notification.FLAG_AUTO_CANCEL | Notification.FLAG_ONLY_ALERT_ONCE; | ||||
|         notifyNotification(notifyId,notification); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 显示下载完成时的通知(点击安装) | ||||
|      * @param notifyId | ||||
|      * @param channelId | ||||
|      * @param icon | ||||
|      * @param title | ||||
|      * @param content | ||||
|      * @param file | ||||
|      */ | ||||
|     private void showFinishNotification(int notifyId,String channelId,@DrawableRes int icon,CharSequence title,CharSequence content,File file,String authority){ | ||||
|         cancelNotification(notifyId); | ||||
|         NotificationCompat.Builder builder = buildNotification(channelId,icon,title,content); | ||||
|         builder.setAutoCancel(true); | ||||
|         Intent intent = new Intent(Intent.ACTION_VIEW); | ||||
|         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|         intent.addCategory(Intent.CATEGORY_DEFAULT); | ||||
|         Uri uriData; | ||||
|         String type = "application/vnd.android.package-archive"; | ||||
|         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){ | ||||
|             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); | ||||
|             uriData = FileProvider.getUriForFile(getContext(), authority, file); | ||||
|         }else{ | ||||
|             uriData = Uri.fromFile(file); | ||||
|         } | ||||
|         intent.setDataAndType(uriData, type); | ||||
|         PendingIntent clickIntent = PendingIntent.getActivity(getContext(), notifyId,intent, PendingIntent.FLAG_UPDATE_CURRENT); | ||||
|         builder.setContentIntent(clickIntent); | ||||
|         Notification notification = builder.build(); | ||||
|         notification.flags = Notification.FLAG_AUTO_CANCEL; | ||||
|         notifyNotification(notifyId,notification); | ||||
|     } | ||||
| 
 | ||||
|     private void showErrorNotification(int notifyId,String channelId,@DrawableRes int icon,CharSequence title,CharSequence content,boolean isReDownload,UpdateConfig config){ | ||||
|         NotificationCompat.Builder builder = buildNotification(channelId,icon,title,content); | ||||
|         builder.setAutoCancel(true); | ||||
|         if(isReDownload){//重新下载
 | ||||
|             Intent intent  = new Intent(getContext(),DownloadService.class); | ||||
|             intent.putExtra(Constants.KEY_RE_DOWNLOAD,true); | ||||
|             intent.putExtra(Constants.KEY_UPDATE_CONFIG,config); | ||||
|             PendingIntent clickIntent = PendingIntent.getService(getContext(), notifyId,intent, PendingIntent.FLAG_UPDATE_CURRENT); | ||||
|             builder.setContentIntent(clickIntent); | ||||
|         }else{ | ||||
|             PendingIntent clickIntent = PendingIntent.getService(getContext(), notifyId,new Intent(), PendingIntent.FLAG_UPDATE_CURRENT); | ||||
|             builder.setContentIntent(clickIntent); | ||||
|         } | ||||
| 
 | ||||
|         Notification notification = builder.build(); | ||||
|         notification.flags = Notification.FLAG_AUTO_CANCEL; | ||||
|         notifyNotification(notifyId,notification); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * 显示通知信息(非第一次) | ||||
|      * @param notifyId | ||||
|      * @param channelId | ||||
|      * @param icon | ||||
|      * @param title | ||||
|      * @param content | ||||
|      */ | ||||
|     private void showNotification(int notifyId,String channelId,@DrawableRes int icon,CharSequence title,CharSequence content,boolean isAutoCancel){ | ||||
|         NotificationCompat.Builder builder = buildNotification(channelId,icon,title,content); | ||||
|         builder.setAutoCancel(isAutoCancel); | ||||
|         Notification notification = builder.build(); | ||||
|         notification.flags = Notification.FLAG_AUTO_CANCEL; | ||||
|         notifyNotification(notifyId,notification); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * | ||||
|      * @param notifyId | ||||
|      */ | ||||
|     private void cancelNotification(int notifyId){ | ||||
|         getNotificationManager().cancel(notifyId); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * 获取通知管理器 | ||||
|      * @return | ||||
|      */ | ||||
|     private NotificationManager getNotificationManager(){ | ||||
|         return (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 创建一个通知渠道(兼容0以上版本) | ||||
|      * @param channelId | ||||
|      * @param channelName | ||||
|      */ | ||||
|     @RequiresApi(api = Build.VERSION_CODES.O) | ||||
|     private void createNotificationChannel(String channelId, String channelName){ | ||||
|         NotificationChannel channel = new NotificationChannel(channelId,channelName, NotificationManager.IMPORTANCE_DEFAULT); | ||||
|         getNotificationManager().createNotificationChannel(channel); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 构建一个通知构建器 | ||||
|      * @param channelId | ||||
|      * @param icon | ||||
|      * @param title | ||||
|      * @param content | ||||
|      * @return | ||||
|      */ | ||||
|     private NotificationCompat.Builder buildNotification(String channelId, @DrawableRes int icon,CharSequence title,CharSequence content){ | ||||
|         return buildNotification(channelId,icon,title,content,Constants.NONE,Constants.NONE); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 构建一个通知构建器 | ||||
|      * @param channelId | ||||
|      * @param icon | ||||
|      * @param title | ||||
|      * @param content | ||||
|      * @param progress | ||||
|      * @param size | ||||
|      * @return | ||||
|      */ | ||||
|     private NotificationCompat.Builder buildNotification(String channelId,@DrawableRes int icon,CharSequence title,CharSequence content,int progress,int size){ | ||||
|         NotificationCompat.Builder builder = new NotificationCompat.Builder(getContext(),channelId); | ||||
|         builder.setSmallIcon(icon); | ||||
| 
 | ||||
|         builder.setContentTitle(title); | ||||
|         builder.setContentText(content); | ||||
|         builder.setOngoing(true); | ||||
| 
 | ||||
|         if(progress!= Constants.NONE && size!=Constants.NONE){ | ||||
|             builder.setProgress(size,progress,false); | ||||
|         } | ||||
| 
 | ||||
|         return builder; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 更新通知栏 | ||||
|      * @param id | ||||
|      * @param notification | ||||
|      */ | ||||
|     private void notifyNotification(int id, Notification notification){ | ||||
|         getNotificationManager().notify(id,notification); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     //---------------------------------------- Binder
 | ||||
| 
 | ||||
|     @Nullable | ||||
|     @Override | ||||
|     public IBinder onBind(Intent intent) { | ||||
|         return mDownloadBinder; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 提供绑定服务的方式下载 | ||||
|      */ | ||||
|     public class DownloadBinder extends Binder { | ||||
| 
 | ||||
|         public void start(UpdateConfig config){ | ||||
|             start(config,null,null); | ||||
|         } | ||||
| 
 | ||||
|         public void start(UpdateConfig config,UpdateCallback callback){ | ||||
|             start(config,null,callback); | ||||
|         } | ||||
| 
 | ||||
|         public void start(UpdateConfig config,IHttpManager httpManager,UpdateCallback callback){ | ||||
|             startDownload(config,httpManager,callback); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,98 @@ | ||||
| package com.king.app.updater.util; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.pm.PackageInfo; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.net.Uri; | ||||
| import android.os.Build; | ||||
| import android.support.v4.content.FileProvider; | ||||
| import android.text.TextUtils; | ||||
| 
 | ||||
| import java.io.File; | ||||
| 
 | ||||
| /** | ||||
|  * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> | ||||
|  */ | ||||
| public enum AppUtils { | ||||
| 
 | ||||
|     INSTANCE; | ||||
| 
 | ||||
|     /** | ||||
|      * | ||||
|      * @param context | ||||
|      * @return AppName.apk | ||||
|      */ | ||||
|     public String getAppFullName(Context context,String url,String defaultName){ | ||||
|         if(url.endsWith(".apk")){ | ||||
|             return url.substring(url.lastIndexOf("/") + 1); | ||||
|         } | ||||
| 
 | ||||
|         String filename = getAppName(context); | ||||
| 
 | ||||
|         if(TextUtils.isEmpty(filename)){ | ||||
|             filename = defaultName; | ||||
|         } | ||||
| 
 | ||||
|         return String.format("%s.apk",filename); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public PackageInfo getPackageInfo(Context context) throws PackageManager.NameNotFoundException { | ||||
|         PackageManager packageManager = context.getPackageManager(); | ||||
|         PackageInfo packageInfo = packageManager.getPackageInfo( context.getPackageName(), 0); | ||||
|         return packageInfo; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * 获取App的名称 | ||||
|      */ | ||||
|     public String getAppName(Context context) { | ||||
|         try{ | ||||
| 
 | ||||
|             int labelRes = getPackageInfo(context).applicationInfo.labelRes; | ||||
|             return context.getResources().getString(labelRes); | ||||
|         } catch (Exception e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取App的图标 | ||||
|      * @param context | ||||
|      * @return | ||||
|      */ | ||||
|     public int getAppIcon(Context context){ | ||||
|         try{ | ||||
|             return getPackageInfo(context).applicationInfo.icon; | ||||
|         } catch (Exception e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
| 
 | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 安装apk | ||||
|      * @param context | ||||
|      * @param file | ||||
|      */ | ||||
|     public void installApk(Context context,File file,String authority){ | ||||
| 
 | ||||
|         Intent intent = new Intent(Intent.ACTION_VIEW); | ||||
|         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||
|         intent.addCategory(Intent.CATEGORY_DEFAULT); | ||||
|         Uri uriData = null; | ||||
|         String type = "application/vnd.android.package-archive"; | ||||
|         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){ | ||||
|             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); | ||||
|             uriData = FileProvider.getUriForFile(context, authority, file); | ||||
|         }else{ | ||||
|             uriData = Uri.fromFile(file); | ||||
|         } | ||||
|         intent.setDataAndType(uriData, type); | ||||
|         context.startActivity(intent); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,36 @@ | ||||
| package com.king.app.updater.util; | ||||
| 
 | ||||
| import android.Manifest; | ||||
| import android.app.Activity; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.support.annotation.NonNull; | ||||
| import android.support.v4.app.ActivityCompat; | ||||
| 
 | ||||
| /** | ||||
|  * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> | ||||
|  */ | ||||
| public enum  PermissionUtils { | ||||
| 
 | ||||
|     INSTANCE; | ||||
| 
 | ||||
|     private String[] PERMISSIONS_STORAGE = { | ||||
|             Manifest.permission.READ_EXTERNAL_STORAGE, | ||||
|             Manifest.permission.WRITE_EXTERNAL_STORAGE, | ||||
|             Manifest.permission.READ_PHONE_STATE}; | ||||
| 
 | ||||
|     public boolean verifyReadAndWritePermissions(@NonNull Activity activity,int requestCode){ | ||||
| 
 | ||||
|         int readResult = checkPermission(activity,Manifest.permission.READ_EXTERNAL_STORAGE); | ||||
|         int writeResult = checkPermission(activity,Manifest.permission.WRITE_EXTERNAL_STORAGE); | ||||
|         int readPhoneState = checkPermission(activity,Manifest.permission.READ_PHONE_STATE); | ||||
|         if( readResult != PackageManager.PERMISSION_GRANTED || writeResult != PackageManager.PERMISSION_GRANTED || readPhoneState != PackageManager.PERMISSION_GRANTED){ | ||||
|             ActivityCompat.requestPermissions(activity,PERMISSIONS_STORAGE,requestCode); | ||||
|             return false; | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     public int checkPermission(@NonNull Activity activity,@NonNull String permission){ | ||||
|         return ActivityCompat.checkSelfPermission(activity,permission); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,175 @@ | ||||
| package com.king.app.updater.util; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.support.annotation.RawRes; | ||||
| 
 | ||||
| import java.io.InputStream; | ||||
| import java.security.KeyManagementException; | ||||
| import java.security.KeyStore; | ||||
| import java.security.NoSuchAlgorithmException; | ||||
| import java.security.SecureRandom; | ||||
| import java.security.cert.Certificate; | ||||
| import java.security.cert.CertificateException; | ||||
| import java.security.cert.CertificateFactory; | ||||
| import java.security.cert.X509Certificate; | ||||
| 
 | ||||
| import javax.net.ssl.HostnameVerifier; | ||||
| import javax.net.ssl.SSLContext; | ||||
| import javax.net.ssl.SSLSession; | ||||
| import javax.net.ssl.SSLSocketFactory; | ||||
| import javax.net.ssl.TrustManager; | ||||
| import javax.net.ssl.TrustManagerFactory; | ||||
| import javax.net.ssl.X509TrustManager; | ||||
| 
 | ||||
| /** | ||||
|  * @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a> | ||||
|  */ | ||||
| public class SSLSocketFactoryUtils { | ||||
| 
 | ||||
|     private SSLSocketFactoryUtils(){ | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public static SSLSocketFactory createSSLSocketFactory() { | ||||
|         SSLSocketFactory sslSocketFactory = null; | ||||
|         try { | ||||
|             SSLContext sslContext = SSLContext.getInstance("TLS"); | ||||
|             sslContext.init(null, new TrustManager[]{createTrustAllManager()}, new SecureRandom()); | ||||
|             sslSocketFactory = sslContext.getSocketFactory(); | ||||
|         } catch (Exception e) { | ||||
| 
 | ||||
|         } | ||||
|         return sslSocketFactory; | ||||
|     } | ||||
| 
 | ||||
|     public static X509TrustManager createTrustAllManager() { | ||||
|         X509TrustManager tm = null; | ||||
|         try { | ||||
|             tm =   new X509TrustManager() { | ||||
|                 public void checkClientTrusted(X509Certificate[] chain, String authType) | ||||
|                         throws CertificateException { | ||||
|                     //do nothing
 | ||||
|                 } | ||||
| 
 | ||||
|                 public void checkServerTrusted(X509Certificate[] chain, String authType) | ||||
|                         throws CertificateException { | ||||
|                     //do nothing
 | ||||
|                 } | ||||
| 
 | ||||
|                 public X509Certificate[] getAcceptedIssuers() { | ||||
|                     return new X509Certificate[0]; | ||||
|                 } | ||||
|             }; | ||||
|         } catch (Exception e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|         return tm; | ||||
|     } | ||||
| 
 | ||||
|     public static TrustAllHostnameVerifier createTrustAllHostnameVerifier(){ | ||||
|         return new TrustAllHostnameVerifier(); | ||||
|     } | ||||
| 
 | ||||
|     public static class TrustAllHostnameVerifier implements HostnameVerifier { | ||||
|         @Override | ||||
|         public boolean verify(String hostname, SSLSession session) { | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * | ||||
|      * @param context | ||||
|      * @param keyServerStoreID | ||||
|      * @return | ||||
|      */ | ||||
|     public static SSLSocketFactory createSSLSocketFactory(Context context,@RawRes int keyServerStoreID){ | ||||
|         InputStream trustStream = context.getResources().openRawResource(keyServerStoreID); | ||||
|         return createSSLSocketFactory(trustStream); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * | ||||
|      * @param certificates | ||||
|      * @return | ||||
|      */ | ||||
|     public static SSLSocketFactory createSSLSocketFactory(InputStream... certificates) { | ||||
|         SSLSocketFactory mSSLSocketFactory = null; | ||||
|         if(mSSLSocketFactory==null){ | ||||
|             synchronized (SSLSocketFactoryUtils.class) { | ||||
|                 if(mSSLSocketFactory==null){ | ||||
| 
 | ||||
|                     SSLContext sslContext = null; | ||||
|                     try { | ||||
|                         sslContext = SSLContext.getInstance("TLS"); | ||||
|                     } catch (NoSuchAlgorithmException e) { | ||||
|                         e.printStackTrace(); | ||||
|                         return null; | ||||
|                     } | ||||
|                     //获得服务器端证书
 | ||||
|                     TrustManager[] turstManager = getTurstManager(certificates); | ||||
| 
 | ||||
|                     //初始化ssl证书库
 | ||||
|                     try { | ||||
|                         sslContext.init(null,turstManager,new SecureRandom()); | ||||
|                     } catch (KeyManagementException e) { | ||||
|                         e.printStackTrace(); | ||||
|                     } | ||||
| 
 | ||||
|                     //获得sslSocketFactory
 | ||||
|                     mSSLSocketFactory = sslContext.getSocketFactory(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return mSSLSocketFactory; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获得指定流中的服务器端证书库 | ||||
|      * @param certificates | ||||
|      * @return | ||||
|      */ | ||||
|     public static TrustManager[] getTurstManager(InputStream... certificates) { | ||||
|         try { | ||||
|             CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); | ||||
|             KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); | ||||
|             keyStore.load(null,null); | ||||
|             int index = 0; | ||||
|             for (InputStream certificate : certificates) { | ||||
|                 if (certificate == null) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 Certificate certificate1; | ||||
|                 try { | ||||
|                     certificate1 = certificateFactory.generateCertificate(certificate); | ||||
|                 }finally { | ||||
|                     certificate.close(); | ||||
|                 } | ||||
| 
 | ||||
|                 String certificateAlias = Integer.toString(index++); | ||||
|                 keyStore.setCertificateEntry(certificateAlias,certificate1); | ||||
|             } | ||||
| 
 | ||||
|             TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory | ||||
|                     .getDefaultAlgorithm()); | ||||
| 
 | ||||
|             trustManagerFactory.init(keyStore); | ||||
|             return trustManagerFactory.getTrustManagers(); | ||||
| 
 | ||||
|         } catch (Exception e) { | ||||
|             e.printStackTrace(); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         return getTurstAllManager(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获得信任所有服务器端证书库 | ||||
|      * */ | ||||
|     public static TrustManager[] getTurstAllManager() { | ||||
|         return new X509TrustManager[] {createTrustAllManager()}; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,18 @@ | ||||
| <resources> | ||||
|     <string name="app_name">AppUpdater</string> | ||||
| 
 | ||||
|     <string name="app_updater_start_notification_title">版本更新</string> | ||||
|     <string name="app_updater_start_notification_content">正在获取下载数据…</string> | ||||
| 
 | ||||
|     <string name="app_updater_progress_notification_title">版本更新</string> | ||||
|     <string name="app_updater_progress_notification_content">下载更新中…</string> | ||||
| 
 | ||||
|     <string name="app_updater_finish_notification_title">下载完成</string> | ||||
|     <string name="app_updater_finish_notification_content">点击安装</string> | ||||
| 
 | ||||
|     <string name="app_updater_error_notification_title">下载失败</string> | ||||
|     <string name="app_updater_error_notification_content">点击关闭通知</string> | ||||
|     <string name="app_updater_error_notification_content_re_download">点击重新下载</string> | ||||
| 
 | ||||
| 
 | ||||
| </resources> | ||||
| @ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <paths> | ||||
|     <external-path name="root_path" path="."/> | ||||
|     <external-path name="app_updater_path" path=".AppUpdater"/> | ||||
|     <cache-path name="data_path" path="data/data"/> | ||||
| </paths> | ||||
| @ -0,0 +1,17 @@ | ||||
| package com.king.app.updater; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| import static org.junit.Assert.*; | ||||
| 
 | ||||
| /** | ||||
|  * Example local unit test, which will execute on the development machine (host). | ||||
|  * | ||||
|  * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> | ||||
|  */ | ||||
| public class ExampleUnitTest { | ||||
|     @Test | ||||
|     public void addition_isCorrect() { | ||||
|         assertEquals(4, 2 + 2); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1 @@ | ||||
| /build | ||||
| @ -0,0 +1,30 @@ | ||||
| apply plugin: 'com.android.application' | ||||
| 
 | ||||
| android { | ||||
|     compileSdkVersion 27 | ||||
|     defaultConfig { | ||||
|         applicationId "com.king.appupdater" | ||||
|         minSdkVersion 15 | ||||
|         targetSdkVersion 27 | ||||
|         versionCode 1 | ||||
|         versionName "1.0" | ||||
|         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" | ||||
|     } | ||||
|     buildTypes { | ||||
|         release { | ||||
|             minifyEnabled false | ||||
|             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| dependencies { | ||||
|     implementation fileTree(include: ['*.jar'], dir: 'libs') | ||||
|     implementation 'com.android.support:appcompat-v7:27.1.1' | ||||
|     implementation 'com.android.support.constraint:constraint-layout:1.1.2' | ||||
|     testImplementation 'junit:junit:4.12' | ||||
|     androidTestImplementation 'com.android.support.test:runner:1.0.2' | ||||
|     androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' | ||||
|     implementation project(':app-updater') | ||||
|     implementation project(':app-dialog') | ||||
| } | ||||
| @ -0,0 +1,21 @@ | ||||
| # Add project specific ProGuard rules here. | ||||
| # You can control the set of applied configuration files using the | ||||
| # proguardFiles setting in build.gradle. | ||||
| # | ||||
| # For more details, see | ||||
| #   http://developer.android.com/guide/developing/tools/proguard.html | ||||
| 
 | ||||
| # If your project uses WebView with JS, uncomment the following | ||||
| # and specify the fully qualified class name to the JavaScript interface | ||||
| # class: | ||||
| #-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||||
| #   public *; | ||||
| #} | ||||
| 
 | ||||
| # Uncomment this to preserve the line number information for | ||||
| # debugging stack traces. | ||||
| #-keepattributes SourceFile,LineNumberTable | ||||
| 
 | ||||
| # If you keep the line number information, uncomment this to | ||||
| # hide the original source file name. | ||||
| #-renamesourcefileattribute SourceFile | ||||
| @ -0,0 +1 @@ | ||||
| [{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] | ||||
| @ -0,0 +1,26 @@ | ||||
| package com.king.appupdater; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.support.test.InstrumentationRegistry; | ||||
| import android.support.test.runner.AndroidJUnit4; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| import org.junit.runner.RunWith; | ||||
| 
 | ||||
| import static org.junit.Assert.*; | ||||
| 
 | ||||
| /** | ||||
|  * Instrumented test, which will execute on an Android device. | ||||
|  * | ||||
|  * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> | ||||
|  */ | ||||
| @RunWith(AndroidJUnit4.class) | ||||
| public class ExampleInstrumentedTest { | ||||
|     @Test | ||||
|     public void useAppContext() { | ||||
|         // Context of the app under test.
 | ||||
|         Context appContext = InstrumentationRegistry.getTargetContext(); | ||||
| 
 | ||||
|         assertEquals("com.king.appupdater", appContext.getPackageName()); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,27 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     package="com.king.appupdater"> | ||||
| 
 | ||||
|     <uses-permission android:name="android.permission.INTERNET" /> | ||||
| 
 | ||||
|     <application | ||||
|         android:allowBackup="true" | ||||
|         android:icon="@mipmap/ic_launcher" | ||||
|         android:label="@string/app_name" | ||||
|         android:roundIcon="@mipmap/ic_launcher" | ||||
|         android:supportsRtl="true" | ||||
|         android:theme="@style/AppTheme"> | ||||
|         <activity android:name=".MainActivity" | ||||
|                   android:screenOrientation="portrait"> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.MAIN" /> | ||||
| 
 | ||||
|                 <category android:name="android.intent.category.LAUNCHER" /> | ||||
|             </intent-filter> | ||||
|         </activity> | ||||
| 
 | ||||
|     </application> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| </manifest> | ||||
| After Width: | Height: | Size: 19 KiB | 
| @ -0,0 +1,274 @@ | ||||
| package com.king.appupdater; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.content.DialogInterface; | ||||
| import android.os.Environment; | ||||
| import android.support.v7.app.AlertDialog; | ||||
| import android.support.v7.app.AppCompatActivity; | ||||
| import android.os.Bundle; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.widget.Button; | ||||
| import android.widget.ProgressBar; | ||||
| import android.widget.TextView; | ||||
| import android.widget.Toast; | ||||
| 
 | ||||
| import com.king.app.dialog.AppDialog; | ||||
| import com.king.app.dialog.AppDialogConfig; | ||||
| import com.king.app.updater.AppUpdater; | ||||
| import com.king.app.updater.UpdateConfig; | ||||
| import com.king.app.updater.callback.AppUpdateCallback; | ||||
| import com.king.app.updater.callback.UpdateCallback; | ||||
| 
 | ||||
| import java.io.File; | ||||
| 
 | ||||
| public class MainActivity extends AppCompatActivity { | ||||
| 
 | ||||
|     private final Object mLock = new Object(); | ||||
| 
 | ||||
|     public final String TAG = this.getClass().getSimpleName(); | ||||
| 
 | ||||
|     private String mUrl = "https://raw.githubusercontent.com/jenly1314/AppUpdater/master/app/release/app-release.apk"; | ||||
| //    private String mUrl = "https://pro-app-qn.fir.im/1ddfe25998acd3d861d746101e6e079e1611b666.apk?attname=app-release.apk_1.2.apk";
 | ||||
| 
 | ||||
|     private ProgressBar progressBar; | ||||
| 
 | ||||
|     private Toast toast; | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         setContentView(R.layout.activity_main); | ||||
|         progressBar = findViewById(R.id.progressBar); | ||||
|         progressBar.setVisibility(View.INVISIBLE); | ||||
|     } | ||||
| 
 | ||||
|     public Context getContext(){ | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public void showToast(String text){ | ||||
|         if(toast == null){ | ||||
|             synchronized (mLock){ | ||||
|                 if(toast == null){ | ||||
|                     toast = Toast.makeText(getContext(),text,Toast.LENGTH_SHORT); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         toast.setText(text); | ||||
|         toast.show(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 简单一键后台升级 | ||||
|      */ | ||||
|     private void clickBtn1(){ | ||||
|         new AppUpdater(getContext(),mUrl).start(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 一键下载并监听 | ||||
|      */ | ||||
|     private void clickBtn2(){ | ||||
|         UpdateConfig config = new UpdateConfig(); | ||||
|         config.setUrl(mUrl); | ||||
|         new AppUpdater(getContext(),config) | ||||
|                 .setUpdateCallback(new UpdateCallback() { | ||||
| 
 | ||||
|                     @Override | ||||
|                     public void onDownloading(boolean isDownloading) { | ||||
|                         if(isDownloading){ | ||||
|                             showToast("已经在下载中,请勿重复下载。"); | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     @Override | ||||
|                     public void onStart(String url) { | ||||
|                         progressBar.setProgress(0); | ||||
|                         progressBar.setVisibility(View.VISIBLE); | ||||
|                     } | ||||
| 
 | ||||
|                     @Override | ||||
|                     public void onProgress(int progress, int total, boolean isChange) { | ||||
|                         if(isChange){ | ||||
|                             progressBar.setMax(total); | ||||
|                             progressBar.setProgress(progress); | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     @Override | ||||
|                     public void onFinish(File file) { | ||||
|                         progressBar.setVisibility(View.INVISIBLE); | ||||
|                     } | ||||
| 
 | ||||
|                     @Override | ||||
|                     public void onError(Exception e) { | ||||
|                         progressBar.setVisibility(View.INVISIBLE); | ||||
|                     } | ||||
| 
 | ||||
|                     @Override | ||||
|                     public void onCancel() { | ||||
|                         progressBar.setVisibility(View.INVISIBLE); | ||||
|                     } | ||||
|                 }) | ||||
|                 .start(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 系统弹框升级 | ||||
|      */ | ||||
|     private void clickBtn3(){ | ||||
|         new AlertDialog.Builder(this) | ||||
|                 .setTitle("发现新版本") | ||||
|                 .setMessage("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、") | ||||
|                 .setPositiveButton("升级", new DialogInterface.OnClickListener() { | ||||
|                     @Override | ||||
|                     public void onClick(DialogInterface dialog, int which) { | ||||
|                         new AppUpdater.Builder() | ||||
|                                 .serUrl(mUrl) | ||||
|                                 .build(getContext()) | ||||
|                                 .setUpdateCallback(new AppUpdateCallback() { | ||||
|                                     @Override | ||||
|                                     public void onProgress(int progress, int total, boolean isChange) { | ||||
| 
 | ||||
|                                     } | ||||
| 
 | ||||
|                                     @Override | ||||
|                                     public void onFinish(File file) { | ||||
|                                         showToast("下载完成"); | ||||
|                                     } | ||||
|                                 }) | ||||
|                                 .start(); | ||||
|                     } | ||||
|                 }).show(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 简单弹框升级 | ||||
|      */ | ||||
|     private void clickBtn4(){ | ||||
|         AppDialogConfig config = new AppDialogConfig(); | ||||
|         config.setTitle("简单弹框升级") | ||||
|                 .setOk("升级") | ||||
|                 .setContent("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、") | ||||
|                 .setOnClickOk(new View.OnClickListener() { | ||||
|                     @Override | ||||
|                     public void onClick(View v) { | ||||
|                         new AppUpdater.Builder() | ||||
|                                 .serUrl(mUrl) | ||||
|                                 .setFilename("AppUpdater.apk") | ||||
|                                 .build(getContext()) | ||||
|                                 .start(); | ||||
|                         AppDialog.INSTANCE.dismissDialog(); | ||||
|                     } | ||||
|                 }); | ||||
|         AppDialog.INSTANCE.showDialog(getContext(),config); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 简单自定义弹框升级 | ||||
|      */ | ||||
|     private void clickBtn5(){ | ||||
|         AppDialogConfig config = new AppDialogConfig(); | ||||
|         config.setLayoutId(R.layout.dialog) | ||||
|                 .setOk("升级") | ||||
|                 .setHideCancel(true) | ||||
|                 .setTitle("简单自定义弹框升级") | ||||
|                 .setContent("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、") | ||||
|                 .setOnClickOk(new View.OnClickListener() { | ||||
|                     @Override | ||||
|                     public void onClick(View v) { | ||||
|                         new AppUpdater.Builder() | ||||
|                                 .serUrl(mUrl) | ||||
|                                 .setFilename(Environment.getExternalStorageDirectory() + "/.AppUpdater") | ||||
|                                 .setFilename("AppUpdater.apk") | ||||
|                                 .build(getContext()) | ||||
|                                 .start(); | ||||
|                         AppDialog.INSTANCE.dismissDialog(); | ||||
|                     } | ||||
|                 }); | ||||
|         AppDialog.INSTANCE.showDialog(getContext(),AppDialog.INSTANCE.createAppDialogView(getContext(),config),false); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 自定义弹框升级 | ||||
|      */ | ||||
|     private void clickBtn6(){ | ||||
|         View view  = LayoutInflater.from(getContext()).inflate(R.layout.dialog_custom,null); | ||||
| 
 | ||||
|         TextView tvTitle = view.findViewById(R.id.tvTitle); | ||||
|         tvTitle.setText("自定义弹框升级"); | ||||
|         TextView tvContent = view.findViewById(R.id.tvContent); | ||||
|         tvContent.setText("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、"); | ||||
| 
 | ||||
|         Button btnCancel = view.findViewById(R.id.btnCancel); | ||||
|         btnCancel.setOnClickListener(new View.OnClickListener() { | ||||
|             @Override | ||||
|             public void onClick(View v) { | ||||
|                 AppDialog.INSTANCE.dismissDialog(); | ||||
|             } | ||||
|         }); | ||||
|         Button btnOK = view.findViewById(R.id.btnOK); | ||||
|         btnOK.setOnClickListener(new View.OnClickListener() { | ||||
|             @Override | ||||
|             public void onClick(View v) { | ||||
|                 new AppUpdater.Builder() | ||||
|                         .serUrl(mUrl) | ||||
|                         .setFilename("AppUpdater.apk") | ||||
|                         .build(getContext()) | ||||
|                         .start(); | ||||
|                 AppDialog.INSTANCE.dismissDialog(); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         AppDialog.INSTANCE.showDialog(getContext(),view); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 简单DialogFragment升级 | ||||
|      */ | ||||
|     private void clickBtn7(){ | ||||
|         AppDialogConfig config = new AppDialogConfig(); | ||||
|         config.setTitle("简单DialogFragment升级") | ||||
|                 .setOk("升级") | ||||
|                 .setContent("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、") | ||||
|                 .setOnClickOk(new View.OnClickListener() { | ||||
|                     @Override | ||||
|                     public void onClick(View v) { | ||||
|                         new AppUpdater.Builder() | ||||
|                                 .serUrl(mUrl) | ||||
|                                 .setFilename("AppUpdater.apk") | ||||
|                                 .build(getContext()) | ||||
|                                 .start(); | ||||
|                         AppDialog.INSTANCE.dismissDialogFragment(getSupportFragmentManager()); | ||||
|                     } | ||||
|                 }); | ||||
|         AppDialog.INSTANCE.showDialogFragment(getSupportFragmentManager(),config); | ||||
|     } | ||||
| 
 | ||||
|     public void OnClick(View v){ | ||||
|         switch (v.getId()){ | ||||
|             case R.id.btn1: | ||||
|                 clickBtn1(); | ||||
|                 break; | ||||
|             case R.id.btn2: | ||||
|                 clickBtn2(); | ||||
|                 break; | ||||
|             case R.id.btn3: | ||||
|                 clickBtn3(); | ||||
|                 break; | ||||
|             case R.id.btn4: | ||||
|                 clickBtn4(); | ||||
|                 break; | ||||
|             case R.id.btn5: | ||||
|                 clickBtn5(); | ||||
|                 break; | ||||
|             case R.id.btn6: | ||||
|                 clickBtn6(); | ||||
|                 break; | ||||
|             case R.id.btn7: | ||||
|                 clickBtn7(); | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,34 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:aapt="http://schemas.android.com/aapt" | ||||
|     android:width="108dp" | ||||
|     android:height="108dp" | ||||
|     android:viewportHeight="108" | ||||
|     android:viewportWidth="108"> | ||||
|     <path | ||||
|         android:fillType="evenOdd" | ||||
|         android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z" | ||||
|         android:strokeColor="#00000000" | ||||
|         android:strokeWidth="1"> | ||||
|         <aapt:attr name="android:fillColor"> | ||||
|             <gradient | ||||
|                 android:endX="78.5885" | ||||
|                 android:endY="90.9159" | ||||
|                 android:startX="48.7653" | ||||
|                 android:startY="61.0927" | ||||
|                 android:type="linear"> | ||||
|                 <item | ||||
|                     android:color="#ffffff" | ||||
|                     android:offset="0.0" /> | ||||
|                 <item | ||||
|                     android:color="#ffffff" | ||||
|                     android:offset="1.0" /> | ||||
|             </gradient> | ||||
|         </aapt:attr> | ||||
|     </path> | ||||
|     <path | ||||
|         android:fillColor="#FFFFFF" | ||||
|         android:fillType="nonZero" | ||||
|         android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z" | ||||
|         android:strokeColor="#00000000" | ||||
|         android:strokeWidth="1" /> | ||||
| </vector> | ||||
| @ -0,0 +1,12 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <shape xmlns:android="http://schemas.android.com/apk/res/android" > | ||||
| 
 | ||||
|     <solid android:color="#F5F5F5" /> | ||||
| 
 | ||||
|     <corners | ||||
|         android:bottomLeftRadius="0dp" | ||||
|         android:bottomRightRadius="0dp" | ||||
|         android:topLeftRadius="10dp" | ||||
|         android:topRightRadius="10dp" /> | ||||
| 
 | ||||
| </shape> | ||||
| @ -0,0 +1,170 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:width="108dp" | ||||
|     android:height="108dp" | ||||
|     android:viewportHeight="108" | ||||
|     android:viewportWidth="108"> | ||||
|     <path | ||||
|         android:fillColor="#ffffff" | ||||
|         android:pathData="M0,0h108v108h-108z" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M9,0L9,108" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,0L19,108" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M29,0L29,108" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M39,0L39,108" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M49,0L49,108" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M59,0L59,108" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M69,0L69,108" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M79,0L79,108" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M89,0L89,108" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M99,0L99,108" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,9L108,9" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,19L108,19" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,29L108,29" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,39L108,39" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,49L108,49" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,59L108,59" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,69L108,69" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,79L108,79" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,89L108,89" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,99L108,99" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,29L89,29" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,39L89,39" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,49L89,49" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,59L89,59" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,69L89,69" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,79L89,79" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M29,19L29,89" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M39,19L39,89" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M49,19L49,89" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M59,19L59,89" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M69,19L69,89" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M79,19L79,89" | ||||
|         android:strokeColor="#33FFFFFF" | ||||
|         android:strokeWidth="0.8" /> | ||||
| </vector> | ||||
| @ -0,0 +1,107 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent" | ||||
|     tools:context=".MainActivity"> | ||||
| 
 | ||||
|     <ProgressBar | ||||
|         android:id="@+id/progressBar" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_margin="20dp" | ||||
|         app:layout_constraintTop_toBottomOf="parent" | ||||
|         app:layout_constraintLeft_toLeftOf="parent" | ||||
|         app:layout_constraintRight_toRightOf="parent" | ||||
|         app:layout_constraintTop_toTopOf="parent" | ||||
|         style="@style/Widget.AppCompat.ProgressBar.Horizontal"/> | ||||
|     <Button | ||||
|         android:id="@+id/btn1" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_marginLeft="20dp" | ||||
|         android:layout_marginRight="20dp" | ||||
|         android:layout_marginTop="40dp" | ||||
|         android:text="简单一键后台升级" | ||||
|         app:layout_constraintTop_toBottomOf="@+id/progressBar" | ||||
|         app:layout_constraintLeft_toLeftOf="parent" | ||||
|         app:layout_constraintRight_toRightOf="parent" | ||||
|         app:layout_constraintTop_toTopOf="parent" | ||||
|         style="@style/OnClick"/> | ||||
|     <Button | ||||
|         android:id="@+id/btn2" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_marginLeft="20dp" | ||||
|         android:layout_marginRight="20dp" | ||||
|         android:layout_marginTop="10dp" | ||||
|         android:text="一键监听进度后台升级" | ||||
|         app:layout_constraintTop_toBottomOf="@+id/btn1" | ||||
|         app:layout_constraintLeft_toLeftOf="parent" | ||||
|         app:layout_constraintRight_toRightOf="parent" | ||||
|         style="@style/OnClick"/> | ||||
|     <Button | ||||
|         android:id="@+id/btn3" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_marginLeft="20dp" | ||||
|         android:layout_marginRight="20dp" | ||||
|         android:layout_marginTop="10dp" | ||||
|         android:text="系统弹框升级" | ||||
|         app:layout_constraintTop_toBottomOf="@+id/btn2" | ||||
|         app:layout_constraintLeft_toLeftOf="parent" | ||||
|         app:layout_constraintRight_toRightOf="parent" | ||||
|         style="@style/OnClick"/> | ||||
|     <Button | ||||
|         android:id="@+id/btn4" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_marginLeft="20dp" | ||||
|         android:layout_marginRight="20dp" | ||||
|         android:layout_marginTop="10dp" | ||||
|         android:text="简单弹框升级" | ||||
|         app:layout_constraintTop_toBottomOf="@+id/btn3" | ||||
|         app:layout_constraintLeft_toLeftOf="parent" | ||||
|         app:layout_constraintRight_toRightOf="parent" | ||||
|         style="@style/OnClick"/> | ||||
|     <Button | ||||
|         android:id="@+id/btn5" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_marginLeft="20dp" | ||||
|         android:layout_marginRight="20dp" | ||||
|         android:layout_marginTop="10dp" | ||||
|         android:text="简单自定义弹框升级" | ||||
|         app:layout_constraintTop_toBottomOf="@+id/btn4" | ||||
|         app:layout_constraintLeft_toLeftOf="parent" | ||||
|         app:layout_constraintRight_toRightOf="parent" | ||||
|         style="@style/OnClick"/> | ||||
| 
 | ||||
|     <Button | ||||
|         android:id="@+id/btn6" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_marginLeft="20dp" | ||||
|         android:layout_marginRight="20dp" | ||||
|         android:layout_marginTop="10dp" | ||||
|         android:text="自定义弹框升级" | ||||
|         app:layout_constraintTop_toBottomOf="@+id/btn5" | ||||
|         app:layout_constraintLeft_toLeftOf="parent" | ||||
|         app:layout_constraintRight_toRightOf="parent" | ||||
|         style="@style/OnClick"/> | ||||
| 
 | ||||
|     <Button | ||||
|         android:id="@+id/btn7" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_marginLeft="20dp" | ||||
|         android:layout_marginRight="20dp" | ||||
|         android:layout_marginTop="10dp" | ||||
|         android:text="简单DialogFragment升级" | ||||
|         app:layout_constraintTop_toBottomOf="@+id/btn6" | ||||
|         app:layout_constraintLeft_toLeftOf="parent" | ||||
|         app:layout_constraintRight_toRightOf="parent" | ||||
|         style="@style/OnClick"/> | ||||
| 
 | ||||
| </android.support.constraint.ConstraintLayout> | ||||
| @ -0,0 +1,60 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="wrap_content" | ||||
|     android:layout_gravity="center" | ||||
|     android:layout_margin="30dp" | ||||
|     android:background="@drawable/app_dialog_bg" | ||||
|     android:orientation="vertical"> | ||||
|     <TextView | ||||
|         android:id="@+id/tvDialogTitle" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:gravity="center" | ||||
|         android:minHeight="40dp" | ||||
|         android:padding="6dp" | ||||
|         android:lines="1" | ||||
|         android:textSize="16sp" | ||||
|         android:background="@drawable/dialog_title_bg" | ||||
|         android:text="@string/app_dialog_title"/> | ||||
|     <TextView | ||||
|         android:id="@+id/tvDialogContent" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:padding="10dp" | ||||
|         android:layout_marginBottom="6dp" | ||||
|         android:lineSpacingMultiplier="1" /> | ||||
|     <include layout="@layout/app_dialog_line_h"/> | ||||
|     <LinearLayout | ||||
|         android:orientation="horizontal" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:gravity="center_horizontal"> | ||||
|         <Button | ||||
|             android:id="@+id/btnDialogCancel" | ||||
|             android:layout_width="0dp" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:paddingTop="10dp" | ||||
|             android:paddingBottom="10dp" | ||||
|             android:layout_weight="1" | ||||
|             android:text="@string/app_dialog_cancel" | ||||
|             android:textSize="18sp" | ||||
|             android:textColor="@color/app_dialog_button_color_selector" | ||||
|             android:background="?android:attr/selectableItemBackground"/> | ||||
|         <include | ||||
|             android:id="@+id/line" | ||||
|             layout="@layout/app_dialog_line_v"/> | ||||
|         <Button | ||||
|             android:id="@+id/btnDialogOK" | ||||
|             android:layout_width="0dp" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:paddingTop="10dp" | ||||
|             android:paddingBottom="10dp" | ||||
|             android:layout_weight="1" | ||||
|             android:text="@string/app_dialog_ok" | ||||
|             android:textSize="18sp" | ||||
|             android:textColor="@color/app_dialog_button_color_selector" | ||||
|             android:background="?android:attr/selectableItemBackground"/> | ||||
|     </LinearLayout> | ||||
| 
 | ||||
| </LinearLayout> | ||||
| @ -0,0 +1,56 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="wrap_content" | ||||
|     android:layout_gravity="center" | ||||
|     android:layout_margin="30dp" | ||||
|     android:background="@android:color/white" | ||||
|     android:orientation="vertical"> | ||||
|     <TextView | ||||
|         android:id="@+id/tvTitle" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:gravity="center" | ||||
|         android:minHeight="40dp" | ||||
|         android:padding="6dp" | ||||
|         android:lines="1" | ||||
|         android:textSize="16sp" | ||||
|         android:background="#F5F5F5" /> | ||||
|     <TextView | ||||
|         android:id="@+id/tvContent" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:padding="10dp" | ||||
|         android:layout_marginBottom="6dp" | ||||
|         android:lineSpacingMultiplier="1" /> | ||||
|     <include layout="@layout/app_dialog_line_h"/> | ||||
|     <LinearLayout | ||||
|         android:orientation="horizontal" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:gravity="center_horizontal"> | ||||
|         <Button | ||||
|             android:id="@+id/btnCancel" | ||||
|             android:layout_width="0dp" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:paddingTop="10dp" | ||||
|             android:paddingBottom="10dp" | ||||
|             android:layout_weight="1" | ||||
|             android:text="@string/app_dialog_cancel" | ||||
|             android:textSize="16sp" | ||||
|             android:textColor="@color/app_dialog_button_color_selector" | ||||
|             android:background="?android:attr/selectableItemBackground"/> | ||||
|         <Button | ||||
|             android:id="@+id/btnOK" | ||||
|             android:layout_width="0dp" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:paddingTop="10dp" | ||||
|             android:paddingBottom="10dp" | ||||
|             android:layout_weight="1" | ||||
|             android:text="升级" | ||||
|             android:textSize="16sp" | ||||
|             android:textColor="@color/app_dialog_button_color_selector" | ||||
|             android:background="?android:attr/selectableItemBackground"/> | ||||
|     </LinearLayout> | ||||
| 
 | ||||
| </LinearLayout> | ||||
| @ -0,0 +1,5 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <background android:drawable="@color/ic_launcher_background"/> | ||||
|     <foreground android:drawable="@mipmap/ic_launcher_foreground"/> | ||||
| </adaptive-icon> | ||||
| @ -0,0 +1,5 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <background android:drawable="@color/ic_launcher_background"/> | ||||
|     <foreground android:drawable="@mipmap/ic_launcher_foreground"/> | ||||
| </adaptive-icon> | ||||
| After Width: | Height: | Size: 1.9 KiB | 
| After Width: | Height: | Size: 2.1 KiB | 
| After Width: | Height: | Size: 3.7 KiB | 
| After Width: | Height: | Size: 1.2 KiB | 
| After Width: | Height: | Size: 1.3 KiB | 
| After Width: | Height: | Size: 2.3 KiB | 
| After Width: | Height: | Size: 2.5 KiB | 
| After Width: | Height: | Size: 2.9 KiB | 
| After Width: | Height: | Size: 5.1 KiB | 
| After Width: | Height: | Size: 3.9 KiB | 
| After Width: | Height: | Size: 4.7 KiB | 
| After Width: | Height: | Size: 8.1 KiB | 
| After Width: | Height: | Size: 5.3 KiB | 
| After Width: | Height: | Size: 6.9 KiB | 
| After Width: | Height: | Size: 12 KiB | 
| @ -0,0 +1,10 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <color name="colorPrimary">#3F51B5</color> | ||||
|     <color name="colorPrimaryDark">#303F9F</color> | ||||
|     <color name="colorAccent">#FF4081</color> | ||||
| 
 | ||||
|     <color name="ic_launcher_background">#FFFFFF</color> | ||||
| 
 | ||||
| 
 | ||||
| </resources> | ||||
| @ -0,0 +1,4 @@ | ||||
| <resources> | ||||
|     <string name="app_name">AppUpdater</string> | ||||
| 
 | ||||
| </resources> | ||||
| @ -0,0 +1,16 @@ | ||||
| <resources> | ||||
| 
 | ||||
|     <!-- Base application theme. --> | ||||
|     <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> | ||||
|         <!-- Customize your theme here. --> | ||||
|         <item name="colorPrimary">@color/colorPrimary</item> | ||||
|         <item name="colorPrimaryDark">@color/colorPrimaryDark</item> | ||||
|         <item name="colorAccent">@color/colorAccent</item> | ||||
|         <item name="android:textAllCaps">false</item> | ||||
|     </style> | ||||
| 
 | ||||
|     <style name="OnClick"> | ||||
|         <item name="android:clickable">true</item> | ||||
|         <item name="android:onClick">OnClick</item> | ||||
|     </style> | ||||
| </resources> | ||||
| @ -0,0 +1,17 @@ | ||||
| package com.king.appupdater; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| import static org.junit.Assert.*; | ||||
| 
 | ||||
| /** | ||||
|  * Example local unit test, which will execute on the development machine (host). | ||||
|  * | ||||
|  * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> | ||||
|  */ | ||||
| public class ExampleUnitTest { | ||||
|     @Test | ||||
|     public void addition_isCorrect() { | ||||
|         assertEquals(4, 2 + 2); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,37 @@ | ||||
| // Top-level build file where you can add configuration options common to all sub-projects/modules. | ||||
| 
 | ||||
| buildscript { | ||||
|      | ||||
|     repositories { | ||||
|         google() | ||||
|         jcenter() | ||||
|     } | ||||
|     dependencies { | ||||
|         classpath 'com.android.tools.build:gradle:3.1.2' | ||||
| 
 | ||||
|         classpath 'com.novoda:bintray-release:0.8.1' | ||||
|         // NOTE: Do not place your application dependencies here; they belong | ||||
|         // in the individual module build.gradle files | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| allprojects { | ||||
|     repositories { | ||||
|         google() | ||||
|         jcenter() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| task clean(type: Delete) { | ||||
|     delete rootProject.buildDir | ||||
| } | ||||
| 
 | ||||
| allprojects { | ||||
|     tasks.withType(Javadoc) { | ||||
|         options{ | ||||
|             encoding "UTF-8" | ||||
|             charSet 'UTF-8' | ||||
|             links "http://docs.oracle.com/javase/8/docs/api" | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,13 @@ | ||||
| # Project-wide Gradle settings. | ||||
| # IDE (e.g. Android Studio) users: | ||||
| # Gradle settings configured through the IDE *will override* | ||||
| # any settings specified in this file. | ||||
| # For more details on how to configure your build environment visit | ||||
| # http://www.gradle.org/docs/current/userguide/build_environment.html | ||||
| # Specifies the JVM arguments used for the daemon process. | ||||
| # The setting is particularly useful for tweaking memory settings. | ||||
| org.gradle.jvmargs=-Xmx1536m | ||||
| # When configured, Gradle will run in incubating parallel mode. | ||||
| # This option should only be used with decoupled projects. More details, visit | ||||
| # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects | ||||
| # org.gradle.parallel=true | ||||
| @ -0,0 +1,6 @@ | ||||
| #Mon Jun 25 18:24:14 CST 2018 | ||||
| distributionBase=GRADLE_USER_HOME | ||||
| distributionPath=wrapper/dists | ||||
| zipStoreBase=GRADLE_USER_HOME | ||||
| zipStorePath=wrapper/dists | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip | ||||
| @ -0,0 +1,172 @@ | ||||
| #!/usr/bin/env sh | ||||
| 
 | ||||
| ############################################################################## | ||||
| ## | ||||
| ##  Gradle start up script for UN*X | ||||
| ## | ||||
| ############################################################################## | ||||
| 
 | ||||
| # Attempt to set APP_HOME | ||||
| # Resolve links: $0 may be a link | ||||
| PRG="$0" | ||||
| # Need this for relative symlinks. | ||||
| while [ -h "$PRG" ] ; do | ||||
|     ls=`ls -ld "$PRG"` | ||||
|     link=`expr "$ls" : '.*-> \(.*\)$'` | ||||
|     if expr "$link" : '/.*' > /dev/null; then | ||||
|         PRG="$link" | ||||
|     else | ||||
|         PRG=`dirname "$PRG"`"/$link" | ||||
|     fi | ||||
| done | ||||
| SAVED="`pwd`" | ||||
| cd "`dirname \"$PRG\"`/" >/dev/null | ||||
| APP_HOME="`pwd -P`" | ||||
| cd "$SAVED" >/dev/null | ||||
| 
 | ||||
| APP_NAME="Gradle" | ||||
| APP_BASE_NAME=`basename "$0"` | ||||
| 
 | ||||
| # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||||
| DEFAULT_JVM_OPTS="" | ||||
| 
 | ||||
| # Use the maximum available, or set MAX_FD != -1 to use that value. | ||||
| MAX_FD="maximum" | ||||
| 
 | ||||
| warn () { | ||||
|     echo "$*" | ||||
| } | ||||
| 
 | ||||
| die () { | ||||
|     echo | ||||
|     echo "$*" | ||||
|     echo | ||||
|     exit 1 | ||||
| } | ||||
| 
 | ||||
| # OS specific support (must be 'true' or 'false'). | ||||
| cygwin=false | ||||
| msys=false | ||||
| darwin=false | ||||
| nonstop=false | ||||
| case "`uname`" in | ||||
|   CYGWIN* ) | ||||
|     cygwin=true | ||||
|     ;; | ||||
|   Darwin* ) | ||||
|     darwin=true | ||||
|     ;; | ||||
|   MINGW* ) | ||||
|     msys=true | ||||
|     ;; | ||||
|   NONSTOP* ) | ||||
|     nonstop=true | ||||
|     ;; | ||||
| esac | ||||
| 
 | ||||
| CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | ||||
| 
 | ||||
| # Determine the Java command to use to start the JVM. | ||||
| if [ -n "$JAVA_HOME" ] ; then | ||||
|     if [ -x "$JAVA_HOME/jre/sh/java" ] ; then | ||||
|         # IBM's JDK on AIX uses strange locations for the executables | ||||
|         JAVACMD="$JAVA_HOME/jre/sh/java" | ||||
|     else | ||||
|         JAVACMD="$JAVA_HOME/bin/java" | ||||
|     fi | ||||
|     if [ ! -x "$JAVACMD" ] ; then | ||||
|         die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME | ||||
| 
 | ||||
| Please set the JAVA_HOME variable in your environment to match the | ||||
| location of your Java installation." | ||||
|     fi | ||||
| else | ||||
|     JAVACMD="java" | ||||
|     which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | ||||
| 
 | ||||
| Please set the JAVA_HOME variable in your environment to match the | ||||
| location of your Java installation." | ||||
| fi | ||||
| 
 | ||||
| # Increase the maximum file descriptors if we can. | ||||
| if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then | ||||
|     MAX_FD_LIMIT=`ulimit -H -n` | ||||
|     if [ $? -eq 0 ] ; then | ||||
|         if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then | ||||
|             MAX_FD="$MAX_FD_LIMIT" | ||||
|         fi | ||||
|         ulimit -n $MAX_FD | ||||
|         if [ $? -ne 0 ] ; then | ||||
|             warn "Could not set maximum file descriptor limit: $MAX_FD" | ||||
|         fi | ||||
|     else | ||||
|         warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" | ||||
|     fi | ||||
| fi | ||||
| 
 | ||||
| # For Darwin, add options to specify how the application appears in the dock | ||||
| if $darwin; then | ||||
|     GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" | ||||
| fi | ||||
| 
 | ||||
| # For Cygwin, switch paths to Windows format before running java | ||||
| if $cygwin ; then | ||||
|     APP_HOME=`cygpath --path --mixed "$APP_HOME"` | ||||
|     CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` | ||||
|     JAVACMD=`cygpath --unix "$JAVACMD"` | ||||
| 
 | ||||
|     # We build the pattern for arguments to be converted via cygpath | ||||
|     ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` | ||||
|     SEP="" | ||||
|     for dir in $ROOTDIRSRAW ; do | ||||
|         ROOTDIRS="$ROOTDIRS$SEP$dir" | ||||
|         SEP="|" | ||||
|     done | ||||
|     OURCYGPATTERN="(^($ROOTDIRS))" | ||||
|     # Add a user-defined pattern to the cygpath arguments | ||||
|     if [ "$GRADLE_CYGPATTERN" != "" ] ; then | ||||
|         OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" | ||||
|     fi | ||||
|     # Now convert the arguments - kludge to limit ourselves to /bin/sh | ||||
|     i=0 | ||||
|     for arg in "$@" ; do | ||||
|         CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` | ||||
|         CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option | ||||
| 
 | ||||
|         if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition | ||||
|             eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` | ||||
|         else | ||||
|             eval `echo args$i`="\"$arg\"" | ||||
|         fi | ||||
|         i=$((i+1)) | ||||
|     done | ||||
|     case $i in | ||||
|         (0) set -- ;; | ||||
|         (1) set -- "$args0" ;; | ||||
|         (2) set -- "$args0" "$args1" ;; | ||||
|         (3) set -- "$args0" "$args1" "$args2" ;; | ||||
|         (4) set -- "$args0" "$args1" "$args2" "$args3" ;; | ||||
|         (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; | ||||
|         (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; | ||||
|         (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; | ||||
|         (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; | ||||
|         (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; | ||||
|     esac | ||||
| fi | ||||
| 
 | ||||
| # Escape application args | ||||
| save () { | ||||
|     for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done | ||||
|     echo " " | ||||
| } | ||||
| APP_ARGS=$(save "$@") | ||||
| 
 | ||||
| # Collect all arguments for the java command, following the shell quoting and substitution rules | ||||
| eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" | ||||
| 
 | ||||
| # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong | ||||
| if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then | ||||
|   cd "$(dirname "$0")" | ||||
| fi | ||||
| 
 | ||||
| exec "$JAVACMD" "$@" | ||||
| @ -0,0 +1,84 @@ | ||||
| @if "%DEBUG%" == "" @echo off | ||||
| @rem ########################################################################## | ||||
| @rem | ||||
| @rem  Gradle startup script for Windows | ||||
| @rem | ||||
| @rem ########################################################################## | ||||
| 
 | ||||
| @rem Set local scope for the variables with windows NT shell | ||||
| if "%OS%"=="Windows_NT" setlocal | ||||
| 
 | ||||
| set DIRNAME=%~dp0 | ||||
| if "%DIRNAME%" == "" set DIRNAME=. | ||||
| set APP_BASE_NAME=%~n0 | ||||
| set APP_HOME=%DIRNAME% | ||||
| 
 | ||||
| @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||||
| set DEFAULT_JVM_OPTS= | ||||
| 
 | ||||
| @rem Find java.exe | ||||
| if defined JAVA_HOME goto findJavaFromJavaHome | ||||
| 
 | ||||
| set JAVA_EXE=java.exe | ||||
| %JAVA_EXE% -version >NUL 2>&1 | ||||
| if "%ERRORLEVEL%" == "0" goto init | ||||
| 
 | ||||
| echo. | ||||
| echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | ||||
| echo. | ||||
| echo Please set the JAVA_HOME variable in your environment to match the | ||||
| echo location of your Java installation. | ||||
| 
 | ||||
| goto fail | ||||
| 
 | ||||
| :findJavaFromJavaHome | ||||
| set JAVA_HOME=%JAVA_HOME:"=% | ||||
| set JAVA_EXE=%JAVA_HOME%/bin/java.exe | ||||
| 
 | ||||
| if exist "%JAVA_EXE%" goto init | ||||
| 
 | ||||
| echo. | ||||
| echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% | ||||
| echo. | ||||
| echo Please set the JAVA_HOME variable in your environment to match the | ||||
| echo location of your Java installation. | ||||
| 
 | ||||
| goto fail | ||||
| 
 | ||||
| :init | ||||
| @rem Get command-line arguments, handling Windows variants | ||||
| 
 | ||||
| if not "%OS%" == "Windows_NT" goto win9xME_args | ||||
| 
 | ||||
| :win9xME_args | ||||
| @rem Slurp the command line arguments. | ||||
| set CMD_LINE_ARGS= | ||||
| set _SKIP=2 | ||||
| 
 | ||||
| :win9xME_args_slurp | ||||
| if "x%~1" == "x" goto execute | ||||
| 
 | ||||
| set CMD_LINE_ARGS=%* | ||||
| 
 | ||||
| :execute | ||||
| @rem Setup the command line | ||||
| 
 | ||||
| set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar | ||||
| 
 | ||||
| @rem Execute Gradle | ||||
| "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% | ||||
| 
 | ||||
| :end | ||||
| @rem End local scope for the variables with windows NT shell | ||||
| if "%ERRORLEVEL%"=="0" goto mainEnd | ||||
| 
 | ||||
| :fail | ||||
| rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of | ||||
| rem the _cmd.exe /c_ return code! | ||||
| if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 | ||||
| exit /b 1 | ||||
| 
 | ||||
| :mainEnd | ||||
| if "%OS%"=="Windows_NT" endlocal | ||||
| 
 | ||||
| :omega | ||||
| @ -0,0 +1 @@ | ||||
| include ':app', ':app-updater', ':app-updater', ':app-dialog' | ||||