(android.content.Context, android.util.AttributeSet, int);
27 | }
28 |
29 | -keepclassmembers class * extends android.app.Activity {
30 | public void *(android.view.View);
31 | }
32 |
33 | -keepclassmembers enum * {
34 | public static **[] values();
35 | public static ** valueOf(java.lang.String);
36 | }
37 |
38 | -keep class * implements android.os.Parcelable {
39 | public static final android.os.Parcelable$Creator *;
40 | }
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | android-auto-updater-client
2 | ===========================
3 |
4 | Android APK auto updater client
5 |
6 | This project allows to automatically update a running APK application using a private update server (see [apk-updater](https://github.com/NDMAC/apk-updater)) instead of Google Play updater.
7 |
8 | __Origin__
9 | Fork of [lenik.terenin](https://code.google.com/u/lenik.terenin/)'s auto-update client https://code.google.com/p/auto-update-apk-client/ for server agnostic usage.
10 |
11 | ## HOW TO
12 |
13 | #### Requirements
14 |
15 | Since you don't put your software on Android Market, you have to enable "Unknown Sources (Allow installation of non-Market applications)" in Settings :: Applications. Otherwise you won't be able to install your software or updates.
16 |
17 | #### Permissions
18 | The following permissions are needed in Android Manifest:
19 |
20 | * android:name="android.permission.INTERNET"
21 | * android:name="android.permission.ACCESS_NETWORK_STATE"
22 | * android:name="android.permission.ACCESS_WIFI_STATE"
23 |
24 | ### Start updater client
25 |
26 | The updater client typically starts during application initialization, simply by creating a new instance.
27 |
28 | ```java
29 | AutoUpdateApk aua = new AutoUpdateApk(getApplicationContext(), UPDATE_URL);
30 | ```
31 |
32 | where UPDATE_URL is your update server URL, eg.
33 | ```java
34 | public final static String UPDATE_URL = "http://mydomain.com/updater";
35 | ```
36 |
37 | ### Configuration
38 |
39 | Updater configuration may be any time after its creation.
40 |
41 | ```java
42 | // auto update every day
43 | aua.setUpdateInterval(AutoUpdateApk.DAYS * 1);
44 | ```
45 |
46 |
--------------------------------------------------------------------------------
/src/com/autoupdateapk/AutoUpdateApkActivity.java:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2012 lenik terenin
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package com.autoupdateapk;
17 |
18 | import java.util.Observable;
19 | import java.util.Observer;
20 |
21 | import com.autoupdateapk.R;
22 |
23 | import android.app.Activity;
24 | import android.os.Bundle;
25 |
26 | public class AutoUpdateApkActivity extends Activity implements Observer {
27 |
28 | // declare updater class member here (or in the Application)
29 | @SuppressWarnings("unused")
30 | private AutoUpdateApk aua;
31 |
32 | @Override
33 | public void onCreate(Bundle savedInstanceState) {
34 | super.onCreate(savedInstanceState);
35 | setContentView(R.layout.main);
36 |
37 | aua = new AutoUpdateApk(getApplicationContext(), AutoUpdateApk.PUBLIC_API_URL); // <-- don't forget to instantiate
38 |
39 | aua.addObserver(this); // see the remark below, next to update() method
40 | }
41 |
42 | // you only need to use this method and specify "implements Observer" and use "addObserver()"
43 | // in case you want to closely monitor what's the AutoUpdateApk is doing, otherwise just ignore
44 | // "implements Observer" and "addObserver()" and skip implementing this method.
45 | //
46 | // There are three kinds of update messages sent from AutoUpdateApk (more may be added later):
47 | // AUTOUPDATE_CHECKING, AUTOUPDATE_NO_UPDATE and AUTOUPDATE_GOT_UPDATE, which denote the start
48 | // of update checking process, and two possible outcomes.
49 | //
50 | @Override
51 | public void update(Observable observable, Object data) {
52 | if( ((String)data).equalsIgnoreCase(AutoUpdateApk.AUTOUPDATE_GOT_UPDATE) ) {
53 | android.util.Log.i("AutoUpdateApkActivity", "Have just received update!");
54 | }
55 | if( ((String)data).equalsIgnoreCase(AutoUpdateApk.AUTOUPDATE_HAVE_UPDATE) ) {
56 | android.util.Log.i("AutoUpdateApkActivity", "There's an update available!");
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/com/autoupdateapk/SilentAutoUpdate.java:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2012 lenik terenin
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package com.autoupdateapk;
17 |
18 | import java.io.DataOutputStream;
19 | import java.io.IOException;
20 | import java.io.InputStream;
21 |
22 | import android.content.Context;
23 | import android.content.pm.ActivityInfo;
24 | import android.content.pm.PackageInfo;
25 | import android.content.pm.PackageManager;
26 | import android.content.pm.PackageManager.NameNotFoundException;
27 |
28 | // ***************************
29 | // *** WARNING *** WARNING ***
30 | // ***************************
31 | //
32 | // this class is very experimental and intended for people who well understand
33 | // what is going on and what kind of unexpected results or strange behavior may
34 | // occur in some cases. please use AutoUpdateApk instead of this class if in doubt
35 |
36 | public class SilentAutoUpdate extends AutoUpdateApk {
37 |
38 | /** If silent install fails, a manual pop-up is provided. */
39 | private boolean manualFallback = true;
40 |
41 | // this class is supposed to be instantiated in any of your activities or,
42 | // better yet, in Application subclass. Something along the lines of:
43 | //
44 | // private SilentAutoUpdate sau; <-- you need to add this line of code
45 | //
46 | // public void onCreate(Bundle savedInstanceState) {
47 | // super.onCreate(savedInstanceState);
48 | // setContentView(R.layout.main);
49 | //
50 | // sau = new SilentAutoUpdate(getApplicationContext()); <-- and add this line too
51 | //
52 | public SilentAutoUpdate(Context ctx, String apiPath, String server) {
53 | super(ctx, apiPath, server);
54 | }
55 |
56 | //
57 | // ---------- everything below this line is private and does not belong to the public API ----------
58 | //
59 | protected void raise_notification() {
60 | String update_file = preferences.getString(UPDATE_FILE, "");
61 | boolean silent_update_failed = preferences.getBoolean(SILENT_FAILED, false);
62 | if( update_file.length() > 0 && !silent_update_failed ) {
63 | final String libs = "LD_LIBRARY_PATH=/vendor/lib:/system/lib ";
64 | final String[] commands = {
65 | libs + "pm install -r " + context.getFilesDir().getAbsolutePath() + "/" + update_file,
66 | libs + "am start -n " + context.getPackageName() + "/" + get_main_activity()
67 | };
68 | execute_as_root(commands); // not supposed to return if successful
69 | preferences.edit().putBoolean(SILENT_FAILED, true).commit(); // avoid silent update loop
70 |
71 | // if silent install fails manual pop-up is used
72 | if(manualFallback){
73 | super.raise_notification();
74 | }
75 | }
76 | super.raise_notification();
77 | }
78 |
79 | // this is not guaranteed to work 100%, should be rewritten.
80 | //
81 | // if your application fails to restart after silent upgrade,
82 | // you may try to replace this function with a simple statement:
83 | //
84 | // return ".YourMainActivity";
85 | //
86 | private String get_main_activity() {
87 | PackageManager pm = context.getPackageManager();
88 | String packageName = context.getPackageName();
89 |
90 | try {
91 | final int flags = PackageManager.GET_ACTIVITIES;
92 | PackageInfo packageInfo = pm.getPackageInfo(packageName, flags);
93 | for( ActivityInfo ai : packageInfo.activities ) {
94 | if( ai.exported ) {
95 | return ai.name;
96 | }
97 | }
98 | } catch (NameNotFoundException e) {
99 | e.printStackTrace();
100 | }
101 | Log_e(TAG, "get_main_activity() failed");
102 | return "";
103 | }
104 |
105 | private void execute_as_root( String[] commands ) {
106 | try {
107 | // Do the magic
108 | Process p = Runtime.getRuntime().exec( "su" );
109 | InputStream es = p.getErrorStream();
110 | DataOutputStream os = new DataOutputStream(p.getOutputStream());
111 |
112 | for( String command : commands ) {
113 | //Log.i(TAG,command);
114 | os.writeBytes(command + "\n");
115 | }
116 | os.writeBytes("exit\n");
117 | os.flush();
118 | os.close();
119 |
120 | int read;
121 | byte[] buffer = new byte[4096];
122 | String output = new String();
123 | while ((read = es.read(buffer)) > 0) {
124 | output += new String(buffer, 0, read);
125 | }
126 |
127 | p.waitFor();
128 | Log_e(TAG, output.trim() + " (" + p.exitValue() + ")");
129 | } catch (IOException e) {
130 | Log_e(TAG, e.getMessage());
131 | } catch (InterruptedException e) {
132 | Log_e(TAG, e.getMessage());
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/src/com/autoupdateapk/AutoUpdateApk.java:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2012 lenik terenin
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | package com.autoupdateapk;
17 |
18 | import java.io.BufferedInputStream;
19 | import java.io.File;
20 | import java.io.FileInputStream;
21 | import java.io.FileOutputStream;
22 | import java.io.IOException;
23 | import java.security.MessageDigest;
24 | import java.util.ArrayList;
25 | import java.util.Calendar;
26 | import java.util.HashSet;
27 | import java.util.LinkedList;
28 | import java.util.List;
29 | import java.util.Observable;
30 | import java.util.Set;
31 | import java.util.zip.CRC32;
32 | import java.util.zip.Checksum;
33 |
34 | import org.apache.http.HttpEntity;
35 | import org.apache.http.ParseException;
36 | import org.apache.http.client.ClientProtocolException;
37 | import org.apache.http.client.methods.HttpGet;
38 | import org.apache.http.client.methods.HttpPost;
39 | import org.apache.http.entity.StringEntity;
40 | import org.apache.http.impl.client.DefaultHttpClient;
41 | import org.apache.http.params.BasicHttpParams;
42 | import org.apache.http.params.HttpConnectionParams;
43 | import org.apache.http.params.HttpParams;
44 | import org.apache.http.util.EntityUtils;
45 |
46 | import android.app.Notification;
47 | import android.app.NotificationManager;
48 | import android.app.PendingIntent;
49 | import android.content.BroadcastReceiver;
50 | import android.content.Context;
51 | import android.content.Intent;
52 | import android.content.IntentFilter;
53 | import android.content.SharedPreferences;
54 | import android.content.pm.ApplicationInfo;
55 | import android.content.pm.PackageInfo;
56 | import android.content.pm.PackageManager;
57 | import android.content.pm.PackageManager.NameNotFoundException;
58 | import android.net.ConnectivityManager;
59 | import android.net.NetworkInfo;
60 | import android.net.Uri;
61 | import android.os.AsyncTask;
62 | import android.os.Handler;
63 | import android.provider.Settings.Secure;
64 | import android.util.Log;
65 |
66 | public class AutoUpdateApk extends Observable {
67 |
68 | /**
69 | * This class is supposed to be instantiated in any of your activities or,
70 | * better yet, in Application subclass. Something along the lines of:
71 | *
72 | *
73 | * private AutoUpdateApk aua; <-- you need to add this line of code
74 | *
75 | * public void onCreate(Bundle savedInstanceState) {
76 | * super.onCreate(savedInstanceState);
77 | * setContentView(R.layout.main);
78 | *
79 | * aua = new AutoUpdateApk(getApplicationContext()); <-- and add this line too
80 | *
81 | *
82 | * @param ctx
83 | * parent activity context
84 | * @param apiURL
85 | * server API path may be relative to server (eg. /myapi/updater)
86 | * or absolute, depending on server implementation : relative
87 | * path and server is mandatory if server's implementation
88 | * provides relative paths. (http://www.auto-update-apk.com/
89 | * provides an existing server at {@link #PUBLIC_API_URL} )
90 | * @param server
91 | * server name and port (eg. myserver.domain.com:8123 ). Should
92 | * be null when using absolutes apiPath.
93 | */
94 | public AutoUpdateApk(Context ctx, String apiPath, String server) {
95 | setupVariables(ctx);
96 | this.server = server;
97 | this.apiPath = apiPath;
98 | }
99 |
100 | public AutoUpdateApk(Context ctx, String apiURL) {
101 | setupVariables(ctx);
102 | this.server = null;
103 | this.apiPath = apiURL;
104 | }
105 |
106 | // set icon for notification popup (default = application icon)
107 | //
108 | public static void setIcon(int icon) {
109 | appIcon = icon;
110 | }
111 |
112 | // set name to display in notification popup (default = application label)
113 | //
114 | public static void setName(String name) {
115 | appName = name;
116 | }
117 |
118 | // set Notification flags (default = Notification.FLAG_AUTO_CANCEL |
119 | // Notification.FLAG_NO_CLEAR)
120 | //
121 | public static void setNotificationFlags(int flags) {
122 | NOTIFICATION_FLAGS = flags;
123 | }
124 |
125 | /**
126 | * set update interval (in milliseconds).
127 | *
128 | * there are nice constants in this file: MINUTES, HOURS, DAYS you may use
129 | * them to specify update interval like: 5 * DAYS
130 | *
131 | * please, don't specify update interval below 1 hour, this might be
132 | * considered annoying behaviour and result in service suspension
133 | */
134 | public void setUpdateInterval(long interval) {
135 | // if( interval > 60 * MINUTES ) {
136 | updateInterval = interval;
137 | // } else {
138 | // Log_e(TAG, "update interval is too short (less than 1 hour)");
139 | // }
140 | }
141 |
142 | // software updates will use WiFi/Ethernet only (default mode)
143 | //
144 | public static void disableMobileUpdates() {
145 | mobile_updates = false;
146 | }
147 |
148 | // software updates will use any internet connection, including mobile
149 | // might be a good idea to have 'unlimited' plan on your 3.75G connection
150 | //
151 | public static void enableMobileUpdates() {
152 | mobile_updates = true;
153 | }
154 |
155 | // call this if you want to perform update on demand
156 | // (checking for updates more often than once an hour is not recommended
157 | // and polling server every few minutes might be a reason for suspension)
158 | //
159 | public void checkUpdatesManually() {
160 | checkUpdates(true); // force update check
161 | }
162 |
163 | public static final String AUTOUPDATE_CHECKING = "autoupdate_checking";
164 | public static final String AUTOUPDATE_NO_UPDATE = "autoupdate_no_update";
165 | public static final String AUTOUPDATE_GOT_UPDATE = "autoupdate_got_update";
166 | public static final String AUTOUPDATE_HAVE_UPDATE = "autoupdate_have_update";
167 |
168 | public static final String PUBLIC_API_URL = "http://www.auto-update-apk.com/check";
169 |
170 | public void clearSchedule() {
171 | schedule.clear();
172 | }
173 |
174 | public void addSchedule(int start, int end) {
175 | schedule.add(new ScheduleEntry(start, end));
176 | }
177 |
178 | //
179 | // ---------- everything below this line is private and does not belong to
180 | // the public API ----------
181 | //
182 | protected final static String TAG = "AutoUpdateApk";
183 |
184 | private final static String ANDROID_PACKAGE = "application/vnd.android.package-archive";
185 |
186 | protected final String server;
187 | protected final String apiPath;
188 |
189 | protected static Context context = null;
190 | protected static SharedPreferences preferences;
191 | private final static String LAST_UPDATE_KEY = "last_update";
192 | private static long last_update = 0;
193 |
194 | private static int appIcon = android.R.drawable.ic_popup_reminder;
195 | private static int versionCode = 0; // as low as it gets
196 | private static String packageName;
197 | private static String appName;
198 | private static int device_id;
199 |
200 | public static final long MINUTES = 60 * 1000;
201 | public static final long HOURS = 60 * MINUTES;
202 | public static final long DAYS = 24 * HOURS;
203 |
204 | // 3-4 hours in dev.mode, 1-2 days for stable releases
205 | private long updateInterval = 3 * HOURS; // how often to check
206 |
207 | private static boolean mobile_updates = false; // download updates over wifi
208 | // only
209 |
210 | private final static Handler updateHandler = new Handler();
211 | protected final static String UPDATE_FILE = "update_file";
212 | protected final static String SILENT_FAILED = "silent_failed";
213 | private final static String MD5_TIME = "md5_time";
214 | private final static String MD5_KEY = "md5";
215 |
216 | private static int NOTIFICATION_ID = 0xDEADBEEF;
217 | private static int NOTIFICATION_FLAGS = Notification.FLAG_AUTO_CANCEL
218 | | Notification.FLAG_NO_CLEAR;
219 | private static long WAKEUP_INTERVAL = 500;
220 |
221 | private class ScheduleEntry {
222 | public int start;
223 | public int end;
224 |
225 | public ScheduleEntry(int start, int end) {
226 | this.start = start;
227 | this.end = end;
228 | }
229 | }
230 |
231 | private static List schedule = new ArrayList();
232 |
233 | private Runnable periodicUpdate = new Runnable() {
234 | @Override
235 | public void run() {
236 | checkUpdates(false);
237 | updateHandler.removeCallbacks(periodicUpdate); // remove whatever
238 | // others may have
239 | // posted
240 | updateHandler.postDelayed(this, WAKEUP_INTERVAL);
241 | }
242 | };
243 |
244 | private BroadcastReceiver connectivity_receiver = new BroadcastReceiver() {
245 | @Override
246 | public void onReceive(Context context, Intent intent) {
247 | NetworkInfo currentNetworkInfo = (NetworkInfo) intent
248 | .getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
249 |
250 | // do application-specific task(s) based on the current network
251 | // state, such
252 | // as enabling queuing of HTTP requests when currentNetworkInfo is
253 | // connected etc.
254 | boolean not_mobile = currentNetworkInfo.getTypeName()
255 | .equalsIgnoreCase("MOBILE") ? false : true;
256 | if (currentNetworkInfo.isConnected()
257 | && (mobile_updates || not_mobile)) {
258 | checkUpdates(false);
259 | updateHandler.postDelayed(periodicUpdate, updateInterval);
260 | } else {
261 | updateHandler.removeCallbacks(periodicUpdate); // no network
262 | // anyway
263 | }
264 | }
265 | };
266 |
267 | private void setupVariables(Context ctx) {
268 | context = ctx;
269 |
270 | packageName = context.getPackageName();
271 | preferences = context.getSharedPreferences(packageName + "_" + TAG,
272 | Context.MODE_PRIVATE);
273 | device_id = crc32(Secure.getString(context.getContentResolver(),
274 | Secure.ANDROID_ID));
275 | last_update = preferences.getLong("last_update", 0);
276 | NOTIFICATION_ID += crc32(packageName);
277 | // schedule.add(new ScheduleEntry(0,24));
278 |
279 | ApplicationInfo appinfo = context.getApplicationInfo();
280 | if (appinfo.icon != 0) {
281 | appIcon = appinfo.icon;
282 | } else {
283 | Log_w(TAG, "unable to find application icon");
284 | }
285 | if (appinfo.labelRes != 0) {
286 | appName = context.getString(appinfo.labelRes);
287 | } else {
288 | Log_w(TAG, "unable to find application label");
289 | }
290 | if (new File(appinfo.sourceDir).lastModified() > preferences.getLong(
291 | MD5_TIME, 0)) {
292 | preferences.edit().putString(MD5_KEY, MD5Hex(appinfo.sourceDir))
293 | .commit();
294 | preferences.edit().putLong(MD5_TIME, System.currentTimeMillis())
295 | .commit();
296 |
297 | String update_file = preferences.getString(UPDATE_FILE, "");
298 | if (update_file.length() > 0) {
299 | if (new File(context.getFilesDir().getAbsolutePath() + "/"
300 | + update_file).delete()) {
301 | preferences.edit().remove(UPDATE_FILE)
302 | .remove(SILENT_FAILED).commit();
303 | }
304 | }
305 | }
306 | raise_notification();
307 |
308 | if (haveInternetPermissions()) {
309 | context.registerReceiver(connectivity_receiver, new IntentFilter(
310 | ConnectivityManager.CONNECTIVITY_ACTION));
311 | }
312 | }
313 |
314 | private boolean checkSchedule() {
315 | if (schedule.size() == 0)
316 | return true; // empty schedule always fits
317 |
318 | int now = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
319 | for (ScheduleEntry e : schedule) {
320 | if (now >= e.start && now < e.end)
321 | return true;
322 | }
323 | return false;
324 | }
325 |
326 | private class CheckUpdateTask extends AsyncTask {
327 | private DefaultHttpClient httpclient = new DefaultHttpClient();
328 | private HttpPost post;
329 |
330 | private List retrieved = new LinkedList();
331 |
332 | public CheckUpdateTask() {
333 | if (server != null) {
334 | post = new HttpPost(server + apiPath);
335 | } else {
336 | post = new HttpPost(apiPath);
337 | }
338 | }
339 |
340 | protected String[] doInBackground(Void... v) {
341 | long start = System.currentTimeMillis();
342 |
343 | HttpParams httpParameters = new BasicHttpParams();
344 | // set the timeout in milliseconds until a connection is established
345 | // the default value is zero, that means the timeout is not used
346 | int timeoutConnection = 3000;
347 | HttpConnectionParams.setConnectionTimeout(httpParameters,
348 | timeoutConnection);
349 | // set the default socket timeout (SO_TIMEOUT) in milliseconds
350 | // which is the timeout for waiting for data
351 | int timeoutSocket = 5000;
352 | HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
353 |
354 | httpclient.setParams(httpParameters);
355 |
356 | try {
357 | StringEntity params = new StringEntity("pkgname=" + packageName
358 | + "&version=" + versionCode + "&md5="
359 | + preferences.getString(MD5_KEY, "0") + "&id="
360 | + String.format("%08x", device_id));
361 | post.setHeader("Content-Type",
362 | "application/x-www-form-urlencoded");
363 | post.setEntity(params);
364 | String response = EntityUtils.toString(httpclient.execute(post)
365 | .getEntity(), "UTF-8");
366 | Log_v(TAG, "got a reply from update server");
367 | String[] result = response.split("\n");
368 | if (result.length > 1
369 | && result[0].equalsIgnoreCase("have update")) {
370 | if (!retrieved.contains(result[1])) {
371 | synchronized (retrieved) {
372 | if (!retrieved.contains(result[1])) {
373 | retrieved.add(result[1]);
374 | HttpGet get = new HttpGet(
375 | (server != null) ? server + result[1]
376 | : result[1]);
377 | HttpEntity entity = httpclient.execute(get)
378 | .getEntity();
379 | Log_v(TAG, "got a package from update server");
380 | if (entity.getContentType().getValue()
381 | .equalsIgnoreCase(ANDROID_PACKAGE)) {
382 | String fname = result[1]
383 | .substring(result[1]
384 | .lastIndexOf('/') + 1)
385 | + ".apk";
386 | FileOutputStream fos = context
387 | .openFileOutput(fname,
388 | Context.MODE_WORLD_READABLE);
389 | entity.writeTo(fos);
390 | fos.close();
391 | result[1] = fname;
392 | }
393 | if (result.length > 2 && result[2] != null) {
394 | try {
395 | versionCode = Integer
396 | .parseInt(result[2]);
397 | } catch (NumberFormatException nfe) {
398 | Log_e(TAG, "Invalide version code", nfe);
399 | }
400 | }
401 | setChanged();
402 | notifyObservers(AUTOUPDATE_GOT_UPDATE);
403 | }
404 | }
405 | }
406 | } else {
407 | setChanged();
408 | notifyObservers(AUTOUPDATE_NO_UPDATE);
409 | Log_v(TAG, "no update available");
410 | }
411 | return result;
412 | } catch (ParseException e) {
413 | Log_e(TAG, e.getMessage());
414 | } catch (ClientProtocolException e) {
415 | Log_e(TAG, e.getMessage());
416 | } catch (IOException e) {
417 | Log_e(TAG, e.getMessage());
418 | } finally {
419 | httpclient.getConnectionManager().shutdown();
420 | long elapsed = System.currentTimeMillis() - start;
421 | Log_v(TAG, "update check finished in " + elapsed + "ms");
422 | }
423 | return null;
424 | }
425 |
426 | protected void onPreExecute() {
427 | // show progress bar or something
428 | Log_v(TAG, "checking if there's update on the server");
429 | }
430 |
431 | protected void onPostExecute(String[] result) {
432 | // kill progress bar here
433 | if (result != null) {
434 | if (result[0].equalsIgnoreCase("have update")) {
435 | preferences.edit().putString(UPDATE_FILE, result[1])
436 | .commit();
437 |
438 | String update_file_path = context.getFilesDir()
439 | .getAbsolutePath() + "/" + result[1];
440 | preferences.edit()
441 | .putString(MD5_KEY, MD5Hex(update_file_path))
442 | .commit();
443 | preferences.edit()
444 | .putLong(MD5_TIME, System.currentTimeMillis())
445 | .commit();
446 | }
447 | raise_notification();
448 | } else {
449 | Log_v(TAG, "no reply from update server");
450 | }
451 | }
452 | }
453 |
454 | private void checkUpdates(boolean forced) {
455 | long now = System.currentTimeMillis();
456 | if (forced || (last_update + updateInterval) < now && checkSchedule()) {
457 | new CheckUpdateTask().execute();
458 | last_update = System.currentTimeMillis();
459 | preferences.edit().putLong(LAST_UPDATE_KEY, last_update).commit();
460 |
461 | this.setChanged();
462 | this.notifyObservers(AUTOUPDATE_CHECKING);
463 | }
464 | }
465 |
466 | protected void raise_notification() {
467 | String ns = Context.NOTIFICATION_SERVICE;
468 | NotificationManager nm = (NotificationManager) context
469 | .getSystemService(ns);
470 |
471 | // nm.cancel( NOTIFICATION_ID ); // tried this, but it just doesn't do
472 | // the trick =(
473 | nm.cancelAll();
474 |
475 | String update_file = preferences.getString(UPDATE_FILE, "");
476 | if (update_file.length() > 0) {
477 | setChanged();
478 | notifyObservers(AUTOUPDATE_HAVE_UPDATE);
479 |
480 | // raise notification
481 | Notification notification = new Notification(appIcon, appName
482 | + " update", System.currentTimeMillis());
483 | notification.flags |= NOTIFICATION_FLAGS;
484 |
485 | CharSequence contentTitle = appName + " update available";
486 | CharSequence contentText = "Select to install";
487 | Intent notificationIntent = new Intent(Intent.ACTION_VIEW);
488 | notificationIntent.setDataAndType(
489 | Uri.parse("file://"
490 | + context.getFilesDir().getAbsolutePath() + "/"
491 | + update_file), ANDROID_PACKAGE);
492 | PendingIntent contentIntent = PendingIntent.getActivity(context, 0,
493 | notificationIntent, 0);
494 |
495 | notification.setLatestEventInfo(context, contentTitle, contentText,
496 | contentIntent);
497 | nm.notify(NOTIFICATION_ID, notification);
498 | } else {
499 | nm.cancel(NOTIFICATION_ID);
500 | }
501 | }
502 |
503 | private String MD5Hex(String filename) {
504 | final int BUFFER_SIZE = 8192;
505 | byte[] buf = new byte[BUFFER_SIZE];
506 | int length;
507 | try {
508 | FileInputStream fis = new FileInputStream(filename);
509 | BufferedInputStream bis = new BufferedInputStream(fis);
510 | MessageDigest md = java.security.MessageDigest.getInstance("MD5");
511 | while ((length = bis.read(buf)) != -1) {
512 | md.update(buf, 0, length);
513 | }
514 | bis.close();
515 |
516 | byte[] array = md.digest();
517 | StringBuffer sb = new StringBuffer();
518 | for (int i = 0; i < array.length; ++i) {
519 | sb.append(Integer.toHexString((array[i] & 0xFF) | 0x100)
520 | .substring(1, 3));
521 | }
522 | Log_v(TAG, "md5sum: " + sb.toString());
523 | return sb.toString();
524 | } catch (Exception e) {
525 | Log_e(TAG, e.getMessage());
526 | }
527 | return "md5bad";
528 | }
529 |
530 | private boolean haveInternetPermissions() {
531 | Set required_perms = new HashSet();
532 | required_perms.add("android.permission.INTERNET");
533 | required_perms.add("android.permission.ACCESS_WIFI_STATE");
534 | required_perms.add("android.permission.ACCESS_NETWORK_STATE");
535 |
536 | PackageManager pm = context.getPackageManager();
537 | String packageName = context.getPackageName();
538 | int flags = PackageManager.GET_PERMISSIONS;
539 | PackageInfo packageInfo = null;
540 |
541 | try {
542 | packageInfo = pm.getPackageInfo(packageName, flags);
543 | versionCode = packageInfo.versionCode;
544 | } catch (NameNotFoundException e) {
545 | Log_e(TAG, e.getMessage());
546 | }
547 |
548 | if (packageInfo.requestedPermissions != null) {
549 | for (String p : packageInfo.requestedPermissions) {
550 | // Log_v(TAG, "permission: " + p.toString());
551 | required_perms.remove(p);
552 | }
553 | if (required_perms.size() == 0) {
554 | return true; // permissions are in order
555 | }
556 | // something is missing
557 | for (String p : required_perms) {
558 | Log_e(TAG, "required permission missing: " + p);
559 | }
560 | }
561 | Log_e(TAG,
562 | "INTERNET/WIFI access required, but no permissions are found in Manifest.xml");
563 | return false;
564 | }
565 |
566 | private static int crc32(String str) {
567 | byte bytes[] = str.getBytes();
568 | Checksum checksum = new CRC32();
569 | checksum.update(bytes, 0, bytes.length);
570 | return (int) checksum.getValue();
571 | }
572 |
573 | // logging facilities to enable easy overriding. thanks, Dan!
574 | //
575 | protected void Log_v(String tag, String message) {
576 | Log_v(tag, message, null);
577 | }
578 |
579 | protected void Log_v(String tag, String message, Throwable e) {
580 | log("v", tag, message, e);
581 | }
582 |
583 | protected void Log_d(String tag, String message) {
584 | Log_d(tag, message, null);
585 | }
586 |
587 | protected void Log_d(String tag, String message, Throwable e) {
588 | log("d", tag, message, e);
589 | }
590 |
591 | protected void Log_i(String tag, String message) {
592 | Log_d(tag, message, null);
593 | }
594 |
595 | protected void Log_i(String tag, String message, Throwable e) {
596 | log("i", tag, message, e);
597 | }
598 |
599 | protected void Log_w(String tag, String message) {
600 | Log_w(tag, message, null);
601 | }
602 |
603 | protected void Log_w(String tag, String message, Throwable e) {
604 | log("w", tag, message, e);
605 | }
606 |
607 | protected void Log_e(String tag, String message) {
608 | Log_e(tag, message, null);
609 | }
610 |
611 | protected void Log_e(String tag, String message, Throwable e) {
612 | log("e", tag, message, e);
613 | }
614 |
615 | protected void log(String level, String tag, String message, Throwable e) {
616 | if (message == null) {
617 | return;
618 | }
619 | if (level.equalsIgnoreCase("v")) {
620 | if (e == null)
621 | android.util.Log.v(tag, message);
622 | else
623 | android.util.Log.v(tag, message, e);
624 | } else if (level.equalsIgnoreCase("d")) {
625 | if (e == null)
626 | android.util.Log.d(tag, message);
627 | else
628 | android.util.Log.d(tag, message, e);
629 | } else if (level.equalsIgnoreCase("i")) {
630 | if (e == null)
631 | android.util.Log.i(tag, message);
632 | else
633 | android.util.Log.i(tag, message, e);
634 | } else if (level.equalsIgnoreCase("w")) {
635 | if (e == null)
636 | android.util.Log.w(tag, message);
637 | else
638 | android.util.Log.w(tag, message, e);
639 | } else {
640 | if (e == null)
641 | android.util.Log.e(tag, message);
642 | else
643 | android.util.Log.e(tag, message, e);
644 | }
645 | }
646 |
647 | }
648 |
--------------------------------------------------------------------------------