Project init
This commit is contained in:
37
Android.mk
Normal file
37
Android.mk
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
LOCAL_PATH:= $(call my-dir)
|
||||||
|
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_MODULE_TAGS := optional
|
||||||
|
|
||||||
|
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||||
|
|
||||||
|
LOCAL_STATIC_JAVA_LIBRARIES := \
|
||||||
|
android-support-design \
|
||||||
|
android-support-v4 \
|
||||||
|
android-support-v7-appcompat \
|
||||||
|
android-support-v7-cardview \
|
||||||
|
android-support-v7-preference \
|
||||||
|
android-support-v7-recyclerview \
|
||||||
|
okhttp
|
||||||
|
|
||||||
|
LOCAL_RESOURCE_DIR := \
|
||||||
|
$(TOP)/frameworks/support/design/res \
|
||||||
|
$(TOP)/frameworks/support/v7/appcompat/res \
|
||||||
|
$(TOP)/frameworks/support/v7/cardview/res \
|
||||||
|
$(TOP)/frameworks/support/v7/preference/res \
|
||||||
|
$(TOP)/frameworks/support/v7/recyclerview/res \
|
||||||
|
$(LOCAL_PATH)/res
|
||||||
|
|
||||||
|
LOCAL_AAPT_FLAGS := \
|
||||||
|
--auto-add-overlay \
|
||||||
|
--extra-packages android.support.design \
|
||||||
|
--extra-packages android.support.v7.appcompat \
|
||||||
|
--extra-packages android.support.v7.cardview \
|
||||||
|
--extra-packages android.support.v7.preference \
|
||||||
|
--extra-packages android.support.v7.recyclerview
|
||||||
|
|
||||||
|
LOCAL_PACKAGE_NAME := Updater
|
||||||
|
LOCAL_PRIVILEGED_MODULE := true
|
||||||
|
|
||||||
|
include $(BUILD_PACKAGE)
|
32
AndroidManifest.xml
Normal file
32
AndroidManifest.xml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="org.lineageos.updater">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="false"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/Theme.AppCompat.Light">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".UpdatesActivity"
|
||||||
|
android:label="@string/display_name"
|
||||||
|
android:launchMode="singleTask">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="com.android.settings.SHORTCUT" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<service android:name=".DownloadService" />
|
||||||
|
|
||||||
|
<receiver android:name=".UpdaterBroadcastReceiver" android:exported="false" />
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
BIN
res/drawable-hdpi/ic_launcher.png
Normal file
BIN
res/drawable-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1010 B |
BIN
res/drawable-mdpi/ic_launcher.png
Normal file
BIN
res/drawable-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 670 B |
BIN
res/drawable-xhdpi/ic_launcher.png
Normal file
BIN
res/drawable-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
res/drawable-xxhdpi/ic_launcher.png
Normal file
BIN
res/drawable-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
res/drawable-xxxhdpi/ic_launcher.png
Normal file
BIN
res/drawable-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
11
res/drawable/ic_system_update.xml
Normal file
11
res/drawable/ic_system_update.xml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M17 1.01L7 1c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM17 19H7V5h10v14zm-1-6h-3V8h-2v5H8l4 4 4-4z" />
|
||||||
|
</vector>
|
13
res/layout/activity_updates.xml
Normal file
13
res/layout/activity_updates.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/main_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<android.support.v7.widget.RecyclerView
|
||||||
|
android:id="@+id/recycler_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scrollbars="vertical" />
|
||||||
|
</FrameLayout>
|
14
res/layout/fragment_updates_list.xml
Normal file
14
res/layout/fragment_updates_list.xml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<android.support.v7.widget.RecyclerView
|
||||||
|
android:id="@+id/recycler_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scrollbars="vertical" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
31
res/layout/update_item_view.xml
Normal file
31
res/layout/update_item_view.xml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?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:orientation="vertical">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button1"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button2"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/download_button" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/name"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/cancel_button" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress_bar"
|
||||||
|
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
4
res/values/config.xml
Normal file
4
res/values/config.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<bool name="download_in_cache">false</bool>
|
||||||
|
</resources>
|
38
res/values/strings.xml
Normal file
38
res/values/strings.xml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2017 The LineageOS Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="app_name">Updater</string>
|
||||||
|
<string name="display_name">Updater</string>
|
||||||
|
|
||||||
|
<string name="download_path_cache" translatable="false">/cache/ota_package/</string>
|
||||||
|
<string name="download_path_data" translatable="false">/data/ota_package/</string>
|
||||||
|
<string name="conf_update_server_url_def" translatable="false">https://download.lineageos.org/api</string>
|
||||||
|
|
||||||
|
<string name="verification_failed_notification">Verification failed</string>
|
||||||
|
<string name="verifying_download_notification">Verifying download</string>
|
||||||
|
<string name="downloading_notification">Downloading</string>
|
||||||
|
<string name="download_paused_notification">Download paused</string>
|
||||||
|
<string name="download_paused_error_notification">Download error</string>
|
||||||
|
<string name="download_completed_notification">Download completed</string>
|
||||||
|
<string name="download_starting_notification">Starting download</string>
|
||||||
|
|
||||||
|
<string name="download_button">Download</string>
|
||||||
|
<string name="pause_button">Pause</string>
|
||||||
|
<string name="resume_button">Resume</string>
|
||||||
|
<string name="cancel_button">Delete</string>
|
||||||
|
<string name="install_button">Install</string>
|
||||||
|
</resources>
|
287
src/org/lineageos/updater/DownloadClient.java
Normal file
287
src/org/lineageos/updater/DownloadClient.java
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 The LineageOS Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.lineageos.updater;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.okhttp.Callback;
|
||||||
|
import com.android.okhttp.Interceptor;
|
||||||
|
import com.android.okhttp.MediaType;
|
||||||
|
import com.android.okhttp.OkHttpClient;
|
||||||
|
import com.android.okhttp.Response;
|
||||||
|
import com.android.okhttp.ResponseBody;
|
||||||
|
import com.android.okhttp.Request;
|
||||||
|
import com.android.okhttp.okio.BufferedSink;
|
||||||
|
import com.android.okhttp.okio.BufferedSource;
|
||||||
|
import com.android.okhttp.okio.Buffer;
|
||||||
|
import com.android.okhttp.okio.ForwardingSource;
|
||||||
|
import com.android.okhttp.okio.Okio;
|
||||||
|
import com.android.okhttp.okio.Source;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class DownloadClient {
|
||||||
|
|
||||||
|
private static final String TAG = "DownloadClient";
|
||||||
|
|
||||||
|
private static final Object DOWNLOAD_TAG = new Object();
|
||||||
|
|
||||||
|
public interface DownloadCallback {
|
||||||
|
void onResponse(int statusCode, String url, Headers headers);
|
||||||
|
void onSuccess(String body);
|
||||||
|
void onFailure();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ProgressListener {
|
||||||
|
void update(long bytesRead, long contentLength, long speed, long eta, boolean done);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Headers {
|
||||||
|
private com.android.okhttp.Headers mHeaders;
|
||||||
|
|
||||||
|
private Headers(com.android.okhttp.Headers headers) {
|
||||||
|
mHeaders = headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String get(String name) {
|
||||||
|
return mHeaders.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, List<String>> getAll() {
|
||||||
|
return mHeaders.toMultimap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final OkHttpClient mClient = new OkHttpClient();
|
||||||
|
|
||||||
|
private long mResumeOffset = 0;
|
||||||
|
|
||||||
|
private DownloadClient() { }
|
||||||
|
|
||||||
|
public static void download(String url, DownloadCallback callback) {
|
||||||
|
DownloadClient downloadClient = new DownloadClient();
|
||||||
|
downloadClient.downloadInternal(url, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DownloadClient downloadFile(String url, File destination,
|
||||||
|
DownloadCallback callback, ProgressListener progressListener) {
|
||||||
|
DownloadClient downloadClient = new DownloadClient();
|
||||||
|
downloadClient.downloadFileInternal(url, destination, callback, progressListener);
|
||||||
|
return downloadClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DownloadClient downloadFile(String url, File destination,
|
||||||
|
DownloadCallback callback) {
|
||||||
|
return downloadFile(url, destination, callback, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DownloadClient downloadFileResume(String url, File destination,
|
||||||
|
DownloadCallback callback, ProgressListener progressListener) {
|
||||||
|
DownloadClient downloadClient = new DownloadClient();
|
||||||
|
downloadClient.downloadFileResumeInternal(url, destination, callback, progressListener);
|
||||||
|
return downloadClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancel() {
|
||||||
|
new AsyncTask<Void, Void, Void>() {
|
||||||
|
protected Void doInBackground(Void... unused) {
|
||||||
|
mClient.cancel(DOWNLOAD_TAG);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadInternal(String url, final DownloadCallback callback) {
|
||||||
|
final Request request = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
mClient.newCall(request).enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Request request, IOException e) {
|
||||||
|
Log.d(TAG, "Download failed", e);
|
||||||
|
callback.onFailure();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Response response) {
|
||||||
|
try {
|
||||||
|
callback.onResponse(response.code(), response.request().urlString(),
|
||||||
|
new Headers(response.headers()));
|
||||||
|
callback.onSuccess(response.body().string());
|
||||||
|
} catch (IOException e) {
|
||||||
|
onFailure(request, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadFileInternal(String url, final File destination,
|
||||||
|
final DownloadCallback callback, final ProgressListener progressListener) {
|
||||||
|
final Request request = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.tag(DOWNLOAD_TAG)
|
||||||
|
.build();
|
||||||
|
downloadFileInternalCommon(request, destination, callback, progressListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadFileResumeInternal(String url, final File destination,
|
||||||
|
final DownloadCallback callback, final ProgressListener progressListener) {
|
||||||
|
final Request.Builder requestBuilder = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.tag(DOWNLOAD_TAG);
|
||||||
|
long offset = destination.length();
|
||||||
|
requestBuilder.addHeader("Range", "bytes=" + offset + "-");
|
||||||
|
final Request request = requestBuilder.build();
|
||||||
|
downloadFileInternalCommon(request, destination, callback, progressListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadFileInternalCommon(final Request request, final File destination,
|
||||||
|
final DownloadCallback callback, final ProgressListener progressListener) {
|
||||||
|
|
||||||
|
mClient.networkInterceptors().add(new Interceptor() {
|
||||||
|
@Override
|
||||||
|
public Response intercept(Chain chain) throws IOException {
|
||||||
|
Response originalResponse = chain.proceed(chain.request());
|
||||||
|
ProgressResponseBody progressResponseBody =
|
||||||
|
new ProgressResponseBody(originalResponse.body(), progressListener);
|
||||||
|
return originalResponse.newBuilder()
|
||||||
|
.body(progressResponseBody)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mClient.newCall(request).enqueue(new Callback() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Request request, IOException e) {
|
||||||
|
Log.d(TAG, "Download failed", e);
|
||||||
|
callback.onFailure();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Response response) {
|
||||||
|
Log.d(TAG, "Downloading");
|
||||||
|
|
||||||
|
final boolean resume = response.code() == 206;
|
||||||
|
if (resume) {
|
||||||
|
mResumeOffset = destination.length();
|
||||||
|
Log.d(TAG, "The server fulfilled the partial content request");
|
||||||
|
}
|
||||||
|
|
||||||
|
callback.onResponse(response.code(), response.request().urlString(),
|
||||||
|
new Headers(response.headers()));
|
||||||
|
try (BufferedSink sink = Okio.buffer(resume ?
|
||||||
|
Okio.appendingSink(destination) : Okio.sink(destination))) {
|
||||||
|
sink.writeAll(response.body().source());
|
||||||
|
Log.d(TAG, "Download complete");
|
||||||
|
sink.flush();
|
||||||
|
callback.onSuccess(null);
|
||||||
|
} catch (IOException e) {
|
||||||
|
onFailure(request, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ProgressResponseBody extends ResponseBody {
|
||||||
|
|
||||||
|
private final ResponseBody mResponseBody;
|
||||||
|
private final ProgressListener mProgressListener;
|
||||||
|
private BufferedSource mBufferedSource;
|
||||||
|
|
||||||
|
ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) {
|
||||||
|
mResponseBody = responseBody;
|
||||||
|
mProgressListener = progressListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MediaType contentType() {
|
||||||
|
return mResponseBody.contentType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long contentLength() throws IOException {
|
||||||
|
return mResponseBody.contentLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BufferedSource source() throws IOException {
|
||||||
|
if (mBufferedSource == null) {
|
||||||
|
mBufferedSource = Okio.buffer(source(mResponseBody.source()));
|
||||||
|
}
|
||||||
|
return mBufferedSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Source source(Source source) {
|
||||||
|
return new ForwardingSource(source) {
|
||||||
|
private long mTotalBytes = 0;
|
||||||
|
private long mTotalBytesRead = mResumeOffset;
|
||||||
|
|
||||||
|
private long mCurSampleBytes = 0;
|
||||||
|
private long mLastMillis = 0;
|
||||||
|
private long mSpeed = -1;
|
||||||
|
private long mEta = -1;
|
||||||
|
|
||||||
|
private void calculateSpeed() {
|
||||||
|
final long millis = SystemClock.elapsedRealtime();
|
||||||
|
final long delta = millis - mLastMillis;
|
||||||
|
if (delta > 500) {
|
||||||
|
final long curSpeed = ((mTotalBytesRead - mCurSampleBytes) * 1000) / delta;
|
||||||
|
if (mSpeed == -1) {
|
||||||
|
mSpeed = curSpeed;
|
||||||
|
} else {
|
||||||
|
mSpeed = ((mSpeed * 3) + curSpeed) / 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
mLastMillis = millis;
|
||||||
|
mCurSampleBytes = mTotalBytesRead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void calculateEta() {
|
||||||
|
if (mSpeed > 0) {
|
||||||
|
mEta = (mTotalBytes - mTotalBytesRead) / mSpeed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long read(Buffer sink, long byteCount) throws IOException {
|
||||||
|
long bytesRead = super.read(sink, byteCount);
|
||||||
|
|
||||||
|
mTotalBytes = mResponseBody.contentLength() + mResumeOffset;
|
||||||
|
// read() returns the number of bytes read, or -1 if this source is exhausted.
|
||||||
|
mTotalBytesRead += bytesRead != -1 ? bytesRead : 0;
|
||||||
|
|
||||||
|
calculateSpeed();
|
||||||
|
calculateEta();
|
||||||
|
|
||||||
|
if (mProgressListener != null) {
|
||||||
|
mProgressListener.update(mTotalBytesRead, mTotalBytes,
|
||||||
|
mSpeed, mEta, bytesRead == -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytesRead;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
357
src/org/lineageos/updater/DownloadController.java
Normal file
357
src/org/lineageos/updater/DownloadController.java
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 The LineageOS Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.lineageos.updater;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.PowerManager;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.lineageos.updater.misc.Utils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class DownloadController implements DownloadControllerInt {
|
||||||
|
|
||||||
|
public static final String PROGRESS_ACTION = "progress_action";
|
||||||
|
public static final String UPDATE_STATUS_ACTION = "update_status_change_action";
|
||||||
|
public static final String DOWNLOAD_ID_EXTRA = "download_id_extra";
|
||||||
|
|
||||||
|
private final String TAG = "DownloadController";
|
||||||
|
|
||||||
|
private static DownloadController sDownloadController;
|
||||||
|
|
||||||
|
private static final int MAX_REPORT_INTERVAL_MS = 1000;
|
||||||
|
|
||||||
|
private final LocalBroadcastManager mBroadcastManager;
|
||||||
|
private final UpdatesDbHelper mUpdatesDbHelper;
|
||||||
|
|
||||||
|
private final PowerManager.WakeLock mWakeLock;
|
||||||
|
|
||||||
|
private final File mDownloadRoot;
|
||||||
|
|
||||||
|
public static DownloadController getInstance() {
|
||||||
|
return sDownloadController;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized DownloadController newInstance(Context context) {
|
||||||
|
if (sDownloadController == null) {
|
||||||
|
sDownloadController = new DownloadController(context);
|
||||||
|
}
|
||||||
|
return sDownloadController;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DownloadController(Context context) {
|
||||||
|
mBroadcastManager = LocalBroadcastManager.getInstance(context);
|
||||||
|
mUpdatesDbHelper = new UpdatesDbHelper(context);
|
||||||
|
mDownloadRoot = Utils.getDownloadPath(context);
|
||||||
|
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||||
|
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Updater");
|
||||||
|
mWakeLock.setReferenceCounted(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DownloadEntry {
|
||||||
|
final UpdateDownload mUpdate;
|
||||||
|
DownloadClient mDownloadClient;
|
||||||
|
private DownloadEntry(UpdateDownload update) {
|
||||||
|
mUpdate = update;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, DownloadEntry> mDownloads = new HashMap<>();
|
||||||
|
|
||||||
|
private void notifyUpdateChange(String downloadId) {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setAction(UPDATE_STATUS_ACTION);
|
||||||
|
intent.putExtra(DOWNLOAD_ID_EXTRA, downloadId);
|
||||||
|
mBroadcastManager.sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyDownloadProgress(String downloadId) {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setAction(PROGRESS_ACTION);
|
||||||
|
intent.putExtra(DOWNLOAD_ID_EXTRA, downloadId);
|
||||||
|
mBroadcastManager.sendBroadcast(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryReleaseWakelock() {
|
||||||
|
if (!hasActiveDownloads()) {
|
||||||
|
mWakeLock.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DownloadClient.DownloadCallback getDownloadCallback(final String downloadId) {
|
||||||
|
return new DownloadClient.DownloadCallback() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(int statusCode, String url, DownloadClient.Headers headers) {
|
||||||
|
UpdateDownload update = mDownloads.get(downloadId).mUpdate;
|
||||||
|
String contentLenght = headers.get("Content-Length");
|
||||||
|
if (contentLenght != null) {
|
||||||
|
try {
|
||||||
|
long size = Long.parseLong(contentLenght);
|
||||||
|
update.setFileSize(size);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Log.e(TAG, "Could not get content-length");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update.setStatus(UpdateStatus.DOWNLOADING);
|
||||||
|
update.setPersistentStatus(UpdateStatus.Persistent.INCOMPLETE);
|
||||||
|
mUpdatesDbHelper.addUpdateWithOnConflict(update);
|
||||||
|
notifyUpdateChange(downloadId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(String body) {
|
||||||
|
UpdateDownload update = mDownloads.get(downloadId).mUpdate;
|
||||||
|
update.setStatus(UpdateStatus.VERIFYING);
|
||||||
|
notifyUpdateChange(downloadId);
|
||||||
|
Log.d(TAG, "Download complete");
|
||||||
|
if (!verifyDownload(update.getFile())) {
|
||||||
|
update.setPersistentStatus(UpdateStatus.Persistent.UNKNOWN);
|
||||||
|
mUpdatesDbHelper.removeUpdate(downloadId);
|
||||||
|
update.setProgress(0);
|
||||||
|
update.setStatus(UpdateStatus.VERIFICATION_FAILED);
|
||||||
|
} else {
|
||||||
|
update.setPersistentStatus(UpdateStatus.Persistent.VERIFIED);
|
||||||
|
mUpdatesDbHelper.changeUpdateStatus(update);
|
||||||
|
update.setStatus(UpdateStatus.VERIFIED);
|
||||||
|
}
|
||||||
|
mDownloads.get(downloadId).mDownloadClient = null;
|
||||||
|
notifyUpdateChange(downloadId);
|
||||||
|
tryReleaseWakelock();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure() {
|
||||||
|
// The client is null if we intentionally stopped the download
|
||||||
|
boolean cancelled = mDownloads.get(downloadId).mDownloadClient == null;
|
||||||
|
UpdateDownload update = mDownloads.get(downloadId).mUpdate;
|
||||||
|
if (cancelled) {
|
||||||
|
Log.d(TAG, "Download cancelled");
|
||||||
|
update.setStatus(UpdateStatus.PAUSED);
|
||||||
|
// Already notified
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Download failed");
|
||||||
|
mDownloads.get(downloadId).mDownloadClient = null;
|
||||||
|
update.setStatus(UpdateStatus.PAUSED_ERROR);
|
||||||
|
notifyUpdateChange(downloadId);
|
||||||
|
}
|
||||||
|
tryReleaseWakelock();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private DownloadClient.ProgressListener getProgressListener(final String downloadId) {
|
||||||
|
return new DownloadClient.ProgressListener() {
|
||||||
|
private long mLastUpdate = 0;
|
||||||
|
private int mProgress = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(long bytesRead, long contentLength, long speed, long eta,
|
||||||
|
boolean done) {
|
||||||
|
final long now = SystemClock.elapsedRealtime();
|
||||||
|
int progress = Math.round(bytesRead * 100 / contentLength);
|
||||||
|
if (progress != mProgress || mLastUpdate - now > MAX_REPORT_INTERVAL_MS) {
|
||||||
|
mProgress = progress;
|
||||||
|
mLastUpdate = now;
|
||||||
|
getUpdate(downloadId).setProgress(progress);
|
||||||
|
getUpdate(downloadId).setEta(eta);
|
||||||
|
notifyDownloadProgress(downloadId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean verifyDownload(File file) {
|
||||||
|
try {
|
||||||
|
android.os.RecoverySystem.verifyPackage(file, null, null);
|
||||||
|
Log.e(TAG, "Verification successful");
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Verification failed", e);
|
||||||
|
if (file.exists()) {
|
||||||
|
file.delete();
|
||||||
|
} else {
|
||||||
|
// The download was probably stopped. Exit silently
|
||||||
|
Log.e(TAG, "Error while verifying the file", e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean fixUpdateStatus(UpdateDownload update) {
|
||||||
|
switch (update.getPersistentStatus()) {
|
||||||
|
case UpdateStatus.Persistent.VERIFIED:
|
||||||
|
case UpdateStatus.Persistent.INCOMPLETE:
|
||||||
|
if (update.getFile() == null || !update.getFile().exists()) {
|
||||||
|
update.setStatus(UpdateStatus.UNKNOWN);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
int progress = Math.round(
|
||||||
|
update.getFile().length() * 100 / update.getFileSize());
|
||||||
|
update.setProgress(progress);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addUpdate(UpdateDownload update, boolean local) {
|
||||||
|
Log.d(TAG, "Adding download: " + update.getDownloadId());
|
||||||
|
if (mDownloads.containsKey(update.getDownloadId())) {
|
||||||
|
Log.e(TAG, "Download (" + update.getDownloadId() + ") already added");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!fixUpdateStatus(update) && local) {
|
||||||
|
update.setPersistentStatus(UpdateStatus.Persistent.UNKNOWN);
|
||||||
|
mUpdatesDbHelper.removeUpdate(update.getDownloadId());
|
||||||
|
Log.d(TAG, update.getDownloadId() + " had an invalid status and is local");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mDownloads.put(update.getDownloadId(), new DownloadEntry(update));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean startDownload(String downloadId) {
|
||||||
|
Log.d(TAG, "Starting " + downloadId);
|
||||||
|
if (!mDownloads.containsKey(downloadId) || isDownloading(downloadId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
UpdateDownload update = mDownloads.get(downloadId).mUpdate;
|
||||||
|
File destination = new File(mDownloadRoot, update.getName());
|
||||||
|
update.setFile(destination);
|
||||||
|
mDownloads.get(downloadId).mDownloadClient =
|
||||||
|
DownloadClient.downloadFile(update.getDownloadUrl(),
|
||||||
|
update.getFile(),
|
||||||
|
getDownloadCallback(downloadId),
|
||||||
|
getProgressListener(downloadId));
|
||||||
|
update.setStatus(UpdateStatus.STARTING);
|
||||||
|
notifyUpdateChange(downloadId);
|
||||||
|
mWakeLock.acquire();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean resumeDownload(String downloadId) {
|
||||||
|
Log.d(TAG, "Resuming " + downloadId);
|
||||||
|
if (!mDownloads.containsKey(downloadId) || isDownloading(downloadId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
UpdateDownload update = mDownloads.get(downloadId).mUpdate;
|
||||||
|
mDownloads.get(downloadId).mDownloadClient =
|
||||||
|
DownloadClient.downloadFileResume(update.getDownloadUrl(),
|
||||||
|
update.getFile(),
|
||||||
|
getDownloadCallback(downloadId),
|
||||||
|
getProgressListener(downloadId));
|
||||||
|
update.setStatus(UpdateStatus.STARTING);
|
||||||
|
notifyUpdateChange(downloadId);
|
||||||
|
mWakeLock.acquire();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean pauseDownload(String downloadId) {
|
||||||
|
Log.d(TAG, "Pausing " + downloadId);
|
||||||
|
if (!isDownloading(downloadId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First remove the client and then cancel the download so that when the download
|
||||||
|
// fails, we know it was intentional
|
||||||
|
DownloadClient downloadClient = mDownloads.get(downloadId).mDownloadClient;
|
||||||
|
mDownloads.get(downloadId).mDownloadClient = null;
|
||||||
|
downloadClient.cancel();
|
||||||
|
UpdateDownload update = mDownloads.get(downloadId).mUpdate;
|
||||||
|
update.setStatus(UpdateStatus.PAUSED);
|
||||||
|
notifyUpdateChange(downloadId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteUpdateAsync(final String downloadId) {
|
||||||
|
new AsyncTask<Void, Void, Void>() {
|
||||||
|
protected Void doInBackground(Void... voids) {
|
||||||
|
UpdateDownload update = mDownloads.get(downloadId).mUpdate;
|
||||||
|
File file = update.getFile();
|
||||||
|
if (file.exists() && !file.delete()) {
|
||||||
|
Log.e(TAG, "Could not delete " + file.getAbsolutePath());
|
||||||
|
}
|
||||||
|
mUpdatesDbHelper.removeUpdate(downloadId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean cancelDownload(String downloadId) {
|
||||||
|
Log.d(TAG, "Cancelling " + downloadId);
|
||||||
|
if (!mDownloads.containsKey(downloadId) || isDownloading(downloadId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
UpdateDownload update = mDownloads.get(downloadId).mUpdate;
|
||||||
|
update.setStatus(UpdateStatus.DELETED);
|
||||||
|
update.setProgress(0);
|
||||||
|
update.setPersistentStatus(UpdateStatus.Persistent.UNKNOWN);
|
||||||
|
deleteUpdateAsync(downloadId);
|
||||||
|
notifyUpdateChange(downloadId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getIds() {
|
||||||
|
return mDownloads.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UpdateDownload> getUpdates() {
|
||||||
|
List<UpdateDownload> updates = new ArrayList<>();
|
||||||
|
for (DownloadEntry entry : mDownloads.values()) {
|
||||||
|
updates.add(entry.mUpdate);
|
||||||
|
}
|
||||||
|
return updates;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UpdateDownload getUpdate(String downloadId) {
|
||||||
|
return mDownloads.get(downloadId).mUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDownloading(String downloadId) {
|
||||||
|
return mDownloads.containsKey(downloadId) &&
|
||||||
|
mDownloads.get(downloadId).mDownloadClient != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasActiveDownloads() {
|
||||||
|
for (DownloadEntry entry : mDownloads.values()) {
|
||||||
|
if (entry.mDownloadClient != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
42
src/org/lineageos/updater/DownloadControllerInt.java
Normal file
42
src/org/lineageos/updater/DownloadControllerInt.java
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 The LineageOS Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.lineageos.updater;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public interface DownloadControllerInt {
|
||||||
|
|
||||||
|
boolean addUpdate(UpdateDownload update, boolean local);
|
||||||
|
|
||||||
|
List<UpdateDownload> getUpdates();
|
||||||
|
|
||||||
|
Set<String> getIds();
|
||||||
|
|
||||||
|
UpdateDownload getUpdate(String downloadId);
|
||||||
|
|
||||||
|
boolean startDownload(String downloadId);
|
||||||
|
|
||||||
|
boolean pauseDownload(String downloadId);
|
||||||
|
|
||||||
|
boolean cancelDownload(String downloadId);
|
||||||
|
|
||||||
|
boolean resumeDownload(String downloadId);
|
||||||
|
|
||||||
|
boolean isDownloading(String downloadId);
|
||||||
|
|
||||||
|
boolean hasActiveDownloads();
|
||||||
|
}
|
266
src/org/lineageos/updater/DownloadService.java
Normal file
266
src/org/lineageos/updater/DownloadService.java
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 The LineageOS Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.lineageos.updater;
|
||||||
|
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import android.support.v7.app.NotificationCompat;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
public class DownloadService extends Service {
|
||||||
|
|
||||||
|
private static final String TAG = "DownloadService";
|
||||||
|
|
||||||
|
private static final int NOTIFICATION_ID = 10;
|
||||||
|
|
||||||
|
private final IBinder mBinder = new LocalBinder();
|
||||||
|
private boolean mHasClients;
|
||||||
|
|
||||||
|
private BroadcastReceiver mBroadcastReceiver;
|
||||||
|
private NotificationCompat.Builder mNotificationBuilder;
|
||||||
|
private NotificationManager mNotificationManager;
|
||||||
|
|
||||||
|
private DownloadControllerInt mDownloadController;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
|
||||||
|
mDownloadController = DownloadController.newInstance(this);
|
||||||
|
|
||||||
|
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||||
|
mNotificationBuilder = new NotificationCompat.Builder(this);
|
||||||
|
mNotificationBuilder.setSmallIcon(R.drawable.ic_system_update);
|
||||||
|
mNotificationBuilder.setShowWhen(false);
|
||||||
|
|
||||||
|
Intent notificationIntent = new Intent(this, UpdatesActivity.class);
|
||||||
|
PendingIntent intent = PendingIntent.getActivity(this, 0, notificationIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
mNotificationBuilder.setContentIntent(intent);
|
||||||
|
|
||||||
|
mBroadcastReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
String downloadId = intent.getStringExtra(DownloadController.DOWNLOAD_ID_EXTRA);
|
||||||
|
if (DownloadController.UPDATE_STATUS_ACTION.equals(intent.getAction())) {
|
||||||
|
UpdateDownload update = mDownloadController.getUpdate(downloadId);
|
||||||
|
handleDownloadStatusChange(update);
|
||||||
|
} else if (DownloadController.PROGRESS_ACTION.equals(intent.getAction())) {
|
||||||
|
UpdateDownload update = mDownloadController.getUpdate(downloadId);
|
||||||
|
int progress = update.getProgress();
|
||||||
|
mNotificationBuilder.setProgress(100, progress, false);
|
||||||
|
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
|
intentFilter.addAction(DownloadController.PROGRESS_ACTION);
|
||||||
|
intentFilter.addAction(DownloadController.UPDATE_STATUS_ACTION);
|
||||||
|
LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, intentFilter);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
LocalBroadcastManager.getInstance(this).unregisterReceiver(mBroadcastReceiver);
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LocalBinder extends Binder {
|
||||||
|
public DownloadService getService() {
|
||||||
|
return DownloadService.this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
mHasClients = true;
|
||||||
|
return mBinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onUnbind(Intent intent) {
|
||||||
|
mHasClients = false;
|
||||||
|
tryStopSelf();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
if (intent == null) {
|
||||||
|
Log.d(TAG, "The service is being restarted, exit");
|
||||||
|
// The service is being restarted, exit without doing anything
|
||||||
|
stopForeground(STOP_FOREGROUND_DETACH);
|
||||||
|
mNotificationBuilder.setOngoing(false);
|
||||||
|
mNotificationManager.cancel(NOTIFICATION_ID);
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Service started");
|
||||||
|
return START_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadControllerInt getDownloadController() {
|
||||||
|
return mDownloadController;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryStopSelf() {
|
||||||
|
if (!mHasClients && !mDownloadController.hasActiveDownloads()) {
|
||||||
|
Log.d(TAG, "Service no longer needed, stopping");
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleDownloadStatusChange(UpdateDownload update) {
|
||||||
|
switch (update.getStatus()) {
|
||||||
|
case DELETED: {
|
||||||
|
stopForeground(STOP_FOREGROUND_DETACH);
|
||||||
|
mNotificationBuilder.setOngoing(false);
|
||||||
|
mNotificationManager.cancel(NOTIFICATION_ID);
|
||||||
|
tryStopSelf();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case STARTING: {
|
||||||
|
mNotificationBuilder.mActions.clear();
|
||||||
|
mNotificationBuilder.setProgress(0, 0, true);
|
||||||
|
String text = getString(R.string.download_starting_notification);
|
||||||
|
NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle()
|
||||||
|
.setBigContentTitle(text).bigText(update.getName());
|
||||||
|
mNotificationBuilder.setStyle(style);
|
||||||
|
mNotificationBuilder.setContentTitle(text);
|
||||||
|
mNotificationBuilder.setTicker(text);
|
||||||
|
mNotificationBuilder.setOngoing(true);
|
||||||
|
startForeground(NOTIFICATION_ID, mNotificationBuilder.build());
|
||||||
|
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DOWNLOADING: {
|
||||||
|
String text = getString(R.string.downloading_notification);
|
||||||
|
NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle()
|
||||||
|
.setBigContentTitle(text).bigText(update.getName());
|
||||||
|
mNotificationBuilder.addAction(com.android.internal.R.drawable.ic_media_pause,
|
||||||
|
getString(R.string.pause_button),
|
||||||
|
getPausePendingIntent(update.getDownloadId()));
|
||||||
|
mNotificationBuilder.setStyle(style);
|
||||||
|
mNotificationBuilder.setContentTitle(text);
|
||||||
|
mNotificationBuilder.setTicker(text);
|
||||||
|
mNotificationBuilder.setOngoing(true);
|
||||||
|
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PAUSED: {
|
||||||
|
stopForeground(STOP_FOREGROUND_DETACH);
|
||||||
|
mNotificationBuilder.mActions.clear();
|
||||||
|
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
|
||||||
|
String text = getString(R.string.download_paused_notification);
|
||||||
|
NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle()
|
||||||
|
.setBigContentTitle(text).bigText(update.getName());
|
||||||
|
mNotificationBuilder.addAction(com.android.internal.R.drawable.ic_media_play,
|
||||||
|
getString(R.string.resume_button),
|
||||||
|
getResumePendingIntent(update.getDownloadId()));
|
||||||
|
mNotificationBuilder.setStyle(style);
|
||||||
|
mNotificationBuilder.setContentTitle(text);
|
||||||
|
mNotificationBuilder.setTicker(text);
|
||||||
|
mNotificationBuilder.setOngoing(false);
|
||||||
|
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PAUSED_ERROR: {
|
||||||
|
stopForeground(STOP_FOREGROUND_DETACH);
|
||||||
|
mNotificationBuilder.mActions.clear();
|
||||||
|
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
|
||||||
|
String text = getString(R.string.download_paused_error_notification);
|
||||||
|
NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle()
|
||||||
|
.setBigContentTitle(text).bigText(update.getName());
|
||||||
|
mNotificationBuilder.addAction(com.android.internal.R.drawable.ic_media_play,
|
||||||
|
getString(R.string.resume_button),
|
||||||
|
getResumePendingIntent(update.getDownloadId()));
|
||||||
|
mNotificationBuilder.setStyle(style);
|
||||||
|
mNotificationBuilder.setContentTitle(text);
|
||||||
|
mNotificationBuilder.setTicker(text);
|
||||||
|
mNotificationBuilder.setOngoing(false);
|
||||||
|
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case VERIFYING: {
|
||||||
|
mNotificationBuilder.setProgress(0, 0, true);
|
||||||
|
mNotificationBuilder.mActions.clear();
|
||||||
|
String text = getString(R.string.verifying_download_notification);
|
||||||
|
NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle()
|
||||||
|
.setBigContentTitle(text).bigText(update.getName());
|
||||||
|
mNotificationBuilder.setStyle(style);
|
||||||
|
mNotificationBuilder.setContentTitle(text);
|
||||||
|
mNotificationBuilder.setTicker(text);
|
||||||
|
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case VERIFIED: {
|
||||||
|
stopForeground(STOP_FOREGROUND_DETACH);
|
||||||
|
mNotificationBuilder.setProgress(100, 100, false);
|
||||||
|
String text = getString(R.string.download_completed_notification);
|
||||||
|
NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle()
|
||||||
|
.setBigContentTitle(text).bigText(update.getName());
|
||||||
|
mNotificationBuilder.setStyle(style);
|
||||||
|
mNotificationBuilder.setContentTitle(text);
|
||||||
|
mNotificationBuilder.setTicker(text);
|
||||||
|
mNotificationBuilder.setOngoing(false);
|
||||||
|
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
|
||||||
|
tryStopSelf();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case VERIFICATION_FAILED: {
|
||||||
|
stopForeground(STOP_FOREGROUND_DETACH);
|
||||||
|
String text = getString(R.string.verification_failed_notification);
|
||||||
|
NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle()
|
||||||
|
.setBigContentTitle(text).bigText(update.getName());
|
||||||
|
mNotificationBuilder.setStyle(style);
|
||||||
|
mNotificationBuilder.setContentTitle(text);
|
||||||
|
mNotificationBuilder.setTicker(text);
|
||||||
|
mNotificationBuilder.setOngoing(false);
|
||||||
|
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
|
||||||
|
tryStopSelf();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PendingIntent getResumePendingIntent(String downloadId) {
|
||||||
|
final Intent intent = new Intent(this, UpdaterBroadcastReceiver.class);
|
||||||
|
intent.setAction(UpdaterBroadcastReceiver.ACTION_DOWNLOAD);
|
||||||
|
intent.putExtra(UpdaterBroadcastReceiver.EXTRA_DOWNLOAD_ID, downloadId);
|
||||||
|
intent.putExtra(UpdaterBroadcastReceiver.EXTRA_DOWNLOAD_ACTION,
|
||||||
|
UpdaterBroadcastReceiver.RESUME);
|
||||||
|
return PendingIntent.getBroadcast(this, 0, intent,
|
||||||
|
PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PendingIntent getPausePendingIntent(String downloadId) {
|
||||||
|
final Intent intent = new Intent(this, UpdaterBroadcastReceiver.class);
|
||||||
|
intent.setAction(UpdaterBroadcastReceiver.ACTION_DOWNLOAD);
|
||||||
|
intent.putExtra(UpdaterBroadcastReceiver.EXTRA_DOWNLOAD_ID, downloadId);
|
||||||
|
intent.putExtra(UpdaterBroadcastReceiver.EXTRA_DOWNLOAD_ACTION,
|
||||||
|
UpdaterBroadcastReceiver.PAUSE);
|
||||||
|
return PendingIntent.getBroadcast(this, 0, intent,
|
||||||
|
PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
}
|
||||||
|
}
|
82
src/org/lineageos/updater/Update.java
Normal file
82
src/org/lineageos/updater/Update.java
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 The LineageOS Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.lineageos.updater;
|
||||||
|
|
||||||
|
public class Update implements Comparable<Update> {
|
||||||
|
|
||||||
|
private String mName;
|
||||||
|
private String mDownloadUrl;
|
||||||
|
private String mDownloadId;
|
||||||
|
private long mTimestamp;
|
||||||
|
private String mType;
|
||||||
|
private String mVersion;
|
||||||
|
|
||||||
|
public Update() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDownloadId() {
|
||||||
|
return mDownloadId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDownloadId(String downloadId) {
|
||||||
|
mDownloadId = downloadId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return mTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimestamp(long timestamp) {
|
||||||
|
mTimestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return mType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
mType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVersion() {
|
||||||
|
return mVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVersion(String version) {
|
||||||
|
mVersion = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDownloadUrl() {
|
||||||
|
return mDownloadUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDownloadUrl(String downloadUrl) {
|
||||||
|
mDownloadUrl = downloadUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Update u) {
|
||||||
|
return mTimestamp < u.mTimestamp ? -1 : mTimestamp > u.mTimestamp ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
76
src/org/lineageos/updater/UpdateDownload.java
Normal file
76
src/org/lineageos/updater/UpdateDownload.java
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 The LineageOS Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.lineageos.updater;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class UpdateDownload extends Update {
|
||||||
|
|
||||||
|
private UpdateStatus mStatus = UpdateStatus.UNKNOWN;
|
||||||
|
private int mPersistentStatus = UpdateStatus.Persistent.UNKNOWN;
|
||||||
|
private File mFile;
|
||||||
|
private long mFileSize;
|
||||||
|
private int mProgress;
|
||||||
|
private long mEta;
|
||||||
|
|
||||||
|
public UpdateStatus getStatus() {
|
||||||
|
return mStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(UpdateStatus status) {
|
||||||
|
mStatus = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPersistentStatus() {
|
||||||
|
return mPersistentStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPersistentStatus(int status) {
|
||||||
|
mPersistentStatus = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getFile() {
|
||||||
|
return mFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFile(File file) {
|
||||||
|
mFile = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getFileSize() {
|
||||||
|
return mFileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFileSize(long fileSize) {
|
||||||
|
mFileSize = fileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProgress() {
|
||||||
|
return mProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProgress(int progress) {
|
||||||
|
mProgress = progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getEta() {
|
||||||
|
return mEta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEta(long eta) {
|
||||||
|
mEta = eta;
|
||||||
|
}
|
||||||
|
}
|
37
src/org/lineageos/updater/UpdateStatus.java
Normal file
37
src/org/lineageos/updater/UpdateStatus.java
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 The LineageOS Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.lineageos.updater;
|
||||||
|
|
||||||
|
public enum UpdateStatus {
|
||||||
|
UNKNOWN,
|
||||||
|
STARTING,
|
||||||
|
DOWNLOADING,
|
||||||
|
DOWNLOADED,
|
||||||
|
PAUSED,
|
||||||
|
PAUSED_ERROR,
|
||||||
|
DELETED,
|
||||||
|
VERIFYING,
|
||||||
|
VERIFIED,
|
||||||
|
VERIFICATION_FAILED,
|
||||||
|
INSTALLING,
|
||||||
|
INSTALLED;
|
||||||
|
|
||||||
|
public static final class Persistent {
|
||||||
|
public static final int UNKNOWN = 0;
|
||||||
|
public static final int INCOMPLETE = 1;
|
||||||
|
public static final int VERIFIED = 2;
|
||||||
|
}
|
||||||
|
}
|
61
src/org/lineageos/updater/UpdaterBroadcastReceiver.java
Normal file
61
src/org/lineageos/updater/UpdaterBroadcastReceiver.java
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 The LineageOS Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.lineageos.updater;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
public class UpdaterBroadcastReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
public static final String ACTION_DOWNLOAD =
|
||||||
|
"org.lineageos.updater.action.DOWNLOAD";
|
||||||
|
public static final String EXTRA_DOWNLOAD_ID =
|
||||||
|
"org.lineageos.updater.extra.DOWNLOAD_ID";
|
||||||
|
public static final String EXTRA_DOWNLOAD_ACTION =
|
||||||
|
"org.lineageos.updater.extra.DOWNLOAD_CHANGE";
|
||||||
|
|
||||||
|
public static final int PAUSE = 0;
|
||||||
|
public static final int RESUME = 1;
|
||||||
|
|
||||||
|
private final static String TAG = "BroadcastReceiver";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
final String action = intent.getAction();
|
||||||
|
if (ACTION_DOWNLOAD.equals(action)) {
|
||||||
|
if (intent.hasExtra(EXTRA_DOWNLOAD_ACTION) && intent.hasExtra(EXTRA_DOWNLOAD_ID)) {
|
||||||
|
DownloadController downloadController = DownloadController.getInstance();
|
||||||
|
if (downloadController == null) {
|
||||||
|
Log.e(TAG, "No download controller instance found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String downloadId = intent.getStringExtra(EXTRA_DOWNLOAD_ID);
|
||||||
|
int requestedAction = intent.getIntExtra(EXTRA_DOWNLOAD_ACTION, -1);
|
||||||
|
if (requestedAction == PAUSE) {
|
||||||
|
downloadController.pauseDownload(downloadId);
|
||||||
|
} else if (requestedAction == RESUME) {
|
||||||
|
downloadController.resumeDownload(downloadId);
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Unknown action");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Missing extra data");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
190
src/org/lineageos/updater/UpdatesActivity.java
Normal file
190
src/org/lineageos/updater/UpdatesActivity.java
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 The LineageOS Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.lineageos.updater;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.support.v7.widget.SimpleItemAnimator;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.lineageos.updater.misc.Utils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class UpdatesActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private static final String TAG = "UpdatesActivity";
|
||||||
|
private DownloadService mDownloadService;
|
||||||
|
private BroadcastReceiver mBroadcastReceiver;
|
||||||
|
|
||||||
|
private UpdatesListAdapter mAdapter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_updates);
|
||||||
|
|
||||||
|
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
|
||||||
|
mAdapter = new UpdatesListAdapter(this);
|
||||||
|
recyclerView.setAdapter(mAdapter);
|
||||||
|
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
|
||||||
|
recyclerView.setLayoutManager(layoutManager);
|
||||||
|
RecyclerView.ItemAnimator animator = recyclerView.getItemAnimator();
|
||||||
|
if (animator instanceof SimpleItemAnimator) {
|
||||||
|
((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
mBroadcastReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (DownloadController.UPDATE_STATUS_ACTION.equals(intent.getAction())) {
|
||||||
|
mAdapter.notifyDataSetChanged();
|
||||||
|
} else if (DownloadController.PROGRESS_ACTION.equals(intent.getAction())) {
|
||||||
|
String downloadId = intent.getStringExtra(DownloadController.DOWNLOAD_ID_EXTRA);
|
||||||
|
mAdapter.notifyItemChanged(downloadId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
Intent intent = new Intent(this, DownloadService.class);
|
||||||
|
startService(intent);
|
||||||
|
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
|
||||||
|
|
||||||
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
|
intentFilter.addAction(DownloadController.UPDATE_STATUS_ACTION);
|
||||||
|
intentFilter.addAction(DownloadController.PROGRESS_ACTION);
|
||||||
|
LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, intentFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
LocalBroadcastManager.getInstance(this).unregisterReceiver(mBroadcastReceiver);
|
||||||
|
if (mDownloadService != null) {
|
||||||
|
unbindService(mConnection);
|
||||||
|
}
|
||||||
|
super.onStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServiceConnection mConnection = new ServiceConnection() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName className,
|
||||||
|
IBinder service) {
|
||||||
|
DownloadService.LocalBinder binder = (DownloadService.LocalBinder) service;
|
||||||
|
mDownloadService = binder.getService();
|
||||||
|
mAdapter.setDownloadController(mDownloadService.getDownloadController());
|
||||||
|
getUpdatesList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName componentName) {
|
||||||
|
mAdapter.setDownloadController(null);
|
||||||
|
mDownloadService = null;
|
||||||
|
mAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private void loadUpdatesList() throws IOException, JSONException {
|
||||||
|
// Process local files first. If they aren't valid, the controller will delete
|
||||||
|
// them from the database. If they are valid, they should be prioritized.
|
||||||
|
|
||||||
|
Log.d(TAG, "Getting updates from internal database");
|
||||||
|
|
||||||
|
DownloadControllerInt controller = mDownloadService.getDownloadController();
|
||||||
|
UpdatesDbHelper dbHelper = new UpdatesDbHelper(this);
|
||||||
|
for (UpdateDownload update : dbHelper.getUpdates()) {
|
||||||
|
controller.addUpdate(update, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Adding remote updates");
|
||||||
|
|
||||||
|
File jsonFile = Utils.getCachedUpdateList(this);
|
||||||
|
for (UpdateDownload update : Utils.parseJson(jsonFile, true)) {
|
||||||
|
controller.addUpdate(update, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> updateIds = new ArrayList<>();
|
||||||
|
updateIds.addAll(controller.getIds());
|
||||||
|
Collections.sort(updateIds);
|
||||||
|
mAdapter.setData(updateIds);
|
||||||
|
mAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getUpdatesList() {
|
||||||
|
File jsonFile = Utils.getCachedUpdateList(this);
|
||||||
|
if (jsonFile.exists()) {
|
||||||
|
try {
|
||||||
|
loadUpdatesList();
|
||||||
|
Log.d(TAG, "Cached list parsed");
|
||||||
|
} catch (IOException | JSONException e) {
|
||||||
|
Log.e(TAG, "Error while parsing json list", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
downloadUpdatesList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadUpdatesList() {
|
||||||
|
File jsonFile = Utils.getCachedUpdateList(this);
|
||||||
|
String url = Utils.getServerURL(this);
|
||||||
|
Log.d(TAG, "Checking " + url);
|
||||||
|
DownloadClient.downloadFile(url, jsonFile, new DownloadClient.DownloadCallback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure() {
|
||||||
|
Log.e(TAG, "Could not download updates list");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(int statusCode, String url,
|
||||||
|
DownloadClient.Headers headers) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(String response) {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "List downloaded");
|
||||||
|
loadUpdatesList();
|
||||||
|
} catch (IOException | JSONException e) {
|
||||||
|
Log.e(TAG, "Could not read json", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
207
src/org/lineageos/updater/UpdatesDbHelper.java
Normal file
207
src/org/lineageos/updater/UpdatesDbHelper.java
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 The LineageOS Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.lineageos.updater;
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
import android.provider.BaseColumns;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class UpdatesDbHelper extends SQLiteOpenHelper {
|
||||||
|
|
||||||
|
public static final int DATABASE_VERSION = 1;
|
||||||
|
public static final String DATABASE_NAME = "updates.db";
|
||||||
|
|
||||||
|
public static class UpdateEntry implements BaseColumns {
|
||||||
|
public static final String TABLE_NAME = "updates";
|
||||||
|
public static final String COLUMN_NAME_STATUS = "status";
|
||||||
|
public static final String COLUMN_NAME_PATH = "path";
|
||||||
|
public static final String COLUMN_NAME_NAME = "name";
|
||||||
|
public static final String COLUMN_NAME_DOWNLOAD_ID = "download_id";
|
||||||
|
public static final String COLUMN_NAME_DOWNLOAD_URL = "download_url";
|
||||||
|
public static final String COLUMN_NAME_TIMESTAMP = "timestamp";
|
||||||
|
public static final String COLUMN_NAME_TYPE = "type";
|
||||||
|
public static final String COLUMN_NAME_VERSION = "version";
|
||||||
|
public static final String COLUMN_NAME_SIZE = "size";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String SQL_CREATE_ENTRIES =
|
||||||
|
"CREATE TABLE " + UpdateEntry.TABLE_NAME + " (" +
|
||||||
|
UpdateEntry._ID + " INTEGER PRIMARY KEY," +
|
||||||
|
UpdateEntry.COLUMN_NAME_STATUS + " INTEGER," +
|
||||||
|
UpdateEntry.COLUMN_NAME_PATH + " TEXT," +
|
||||||
|
UpdateEntry.COLUMN_NAME_NAME + " TEXT," +
|
||||||
|
UpdateEntry.COLUMN_NAME_DOWNLOAD_ID + " TEXT NOT NULL UNIQUE," +
|
||||||
|
UpdateEntry.COLUMN_NAME_DOWNLOAD_URL + " TEXT," +
|
||||||
|
UpdateEntry.COLUMN_NAME_TIMESTAMP + " INTEGER," +
|
||||||
|
UpdateEntry.COLUMN_NAME_TYPE + " TEXT," +
|
||||||
|
UpdateEntry.COLUMN_NAME_VERSION + " TEXT," +
|
||||||
|
UpdateEntry.COLUMN_NAME_SIZE + " INTEGER)";
|
||||||
|
|
||||||
|
private static final String SQL_DELETE_ENTRIES =
|
||||||
|
"DROP TABLE IF EXISTS " + UpdateEntry.TABLE_NAME;
|
||||||
|
|
||||||
|
public UpdatesDbHelper(Context context) {
|
||||||
|
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCreate(SQLiteDatabase db) {
|
||||||
|
db.execSQL(SQL_CREATE_ENTRIES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
|
db.execSQL(SQL_DELETE_ENTRIES);
|
||||||
|
onCreate(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
|
onUpgrade(db, oldVersion, newVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long addUpdate(UpdateDownload update) {
|
||||||
|
SQLiteDatabase db = getWritableDatabase();
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(UpdateEntry.COLUMN_NAME_STATUS, update.getPersistentStatus());
|
||||||
|
values.put(UpdateEntry.COLUMN_NAME_PATH, update.getFile().getAbsolutePath());
|
||||||
|
values.put(UpdateEntry.COLUMN_NAME_NAME, update.getName());
|
||||||
|
values.put(UpdateEntry.COLUMN_NAME_DOWNLOAD_ID, update.getDownloadId());
|
||||||
|
values.put(UpdateEntry.COLUMN_NAME_DOWNLOAD_URL, update.getDownloadUrl());
|
||||||
|
values.put(UpdateEntry.COLUMN_NAME_TIMESTAMP, update.getTimestamp());
|
||||||
|
values.put(UpdateEntry.COLUMN_NAME_TYPE, update.getType());
|
||||||
|
values.put(UpdateEntry.COLUMN_NAME_VERSION, update.getVersion());
|
||||||
|
values.put(UpdateEntry.COLUMN_NAME_SIZE, update.getFileSize());
|
||||||
|
return db.insert(UpdateEntry.TABLE_NAME, null, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long addUpdateWithOnConflict(UpdateDownload update) {
|
||||||
|
SQLiteDatabase db = getWritableDatabase();
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(UpdateEntry.COLUMN_NAME_STATUS, update.getPersistentStatus());
|
||||||
|
values.put(UpdateEntry.COLUMN_NAME_PATH, update.getFile().getAbsolutePath());
|
||||||
|
values.put(UpdateEntry.COLUMN_NAME_NAME, update.getName());
|
||||||
|
values.put(UpdateEntry.COLUMN_NAME_DOWNLOAD_ID, update.getDownloadId());
|
||||||
|
values.put(UpdateEntry.COLUMN_NAME_DOWNLOAD_URL, update.getDownloadUrl());
|
||||||
|
values.put(UpdateEntry.COLUMN_NAME_TIMESTAMP, update.getTimestamp());
|
||||||
|
values.put(UpdateEntry.COLUMN_NAME_TYPE, update.getType());
|
||||||
|
values.put(UpdateEntry.COLUMN_NAME_VERSION, update.getVersion());
|
||||||
|
values.put(UpdateEntry.COLUMN_NAME_SIZE, update.getFileSize());
|
||||||
|
return db.insertWithOnConflict(UpdateEntry.TABLE_NAME, null, values,
|
||||||
|
SQLiteDatabase.CONFLICT_IGNORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeUpdate(String downloadId) {
|
||||||
|
SQLiteDatabase db = getWritableDatabase();
|
||||||
|
String selection = UpdateEntry.COLUMN_NAME_DOWNLOAD_ID + " = ?";
|
||||||
|
String[] selectionArgs = {downloadId};
|
||||||
|
return db.delete(UpdateEntry.TABLE_NAME, selection, selectionArgs) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeUpdate(long rowId) {
|
||||||
|
SQLiteDatabase db = getWritableDatabase();
|
||||||
|
String selection = UpdateEntry._ID + " = " + rowId;
|
||||||
|
return db.delete(UpdateEntry.TABLE_NAME, selection, null) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean changeUpdateStatus(UpdateDownload update) {
|
||||||
|
String selection = UpdateEntry.COLUMN_NAME_DOWNLOAD_ID + " = ?";
|
||||||
|
String[] selectionArgs = {update.getDownloadId()};
|
||||||
|
return changeUpdateStatus(selection, selectionArgs, update.getPersistentStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean changeUpdateStatus(long rowId, int status) {
|
||||||
|
String selection = UpdateEntry._ID + " = " + rowId;
|
||||||
|
return changeUpdateStatus(selection, null, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean changeUpdateStatus(String selection, String[] selectionArgs,
|
||||||
|
int status) {
|
||||||
|
SQLiteDatabase db = getWritableDatabase();
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(UpdateEntry.COLUMN_NAME_STATUS, status);
|
||||||
|
return db.update(UpdateEntry.TABLE_NAME, values, selection, selectionArgs) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpdateDownload getUpdate(long rowId) {
|
||||||
|
String selection = UpdateEntry._ID + " = " + rowId;
|
||||||
|
return getUpdate(selection, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpdateDownload getUpdate(String downloadId) {
|
||||||
|
String selection = UpdateEntry.COLUMN_NAME_DOWNLOAD_ID + " = ?";
|
||||||
|
String[] selectionArgs = {downloadId};
|
||||||
|
return getUpdate(selection, selectionArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private UpdateDownload getUpdate(String selection, String[] selectionArgs) {
|
||||||
|
List<UpdateDownload> updates = getUpdates(selection, selectionArgs);
|
||||||
|
return updates != null ? updates.get(0) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<UpdateDownload> getUpdates() {
|
||||||
|
return getUpdates(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<UpdateDownload> getUpdates(String selection, String[] selectionArgs) {
|
||||||
|
SQLiteDatabase db = getReadableDatabase();
|
||||||
|
String[] projection = {
|
||||||
|
UpdateEntry.COLUMN_NAME_PATH,
|
||||||
|
UpdateEntry.COLUMN_NAME_NAME,
|
||||||
|
UpdateEntry.COLUMN_NAME_DOWNLOAD_ID,
|
||||||
|
UpdateEntry.COLUMN_NAME_DOWNLOAD_URL,
|
||||||
|
UpdateEntry.COLUMN_NAME_TIMESTAMP,
|
||||||
|
UpdateEntry.COLUMN_NAME_TYPE,
|
||||||
|
UpdateEntry.COLUMN_NAME_VERSION,
|
||||||
|
UpdateEntry.COLUMN_NAME_STATUS,
|
||||||
|
UpdateEntry.COLUMN_NAME_SIZE,
|
||||||
|
};
|
||||||
|
String sort = UpdateEntry.COLUMN_NAME_TIMESTAMP + " DESC";
|
||||||
|
Cursor cursor = db.query(UpdateEntry.TABLE_NAME, projection, selection, selectionArgs,
|
||||||
|
null, null, sort);
|
||||||
|
List<UpdateDownload> updates = new ArrayList<>();
|
||||||
|
if (cursor != null) {
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
UpdateDownload update = new UpdateDownload();
|
||||||
|
int index = cursor.getColumnIndex(UpdateEntry.COLUMN_NAME_PATH);
|
||||||
|
update.setFile(new File(cursor.getString(index)));
|
||||||
|
index = cursor.getColumnIndex(UpdateEntry.COLUMN_NAME_NAME);
|
||||||
|
update.setName(cursor.getString(index));
|
||||||
|
index = cursor.getColumnIndex(UpdateEntry.COLUMN_NAME_DOWNLOAD_ID);
|
||||||
|
update.setDownloadId(cursor.getString(index));
|
||||||
|
index = cursor.getColumnIndex(UpdateEntry.COLUMN_NAME_DOWNLOAD_URL);
|
||||||
|
update.setDownloadUrl(cursor.getString(index));
|
||||||
|
index = cursor.getColumnIndex(UpdateEntry.COLUMN_NAME_TIMESTAMP);
|
||||||
|
update.setTimestamp(cursor.getLong(index));
|
||||||
|
index = cursor.getColumnIndex(UpdateEntry.COLUMN_NAME_TYPE);
|
||||||
|
update.setType(cursor.getString(index));
|
||||||
|
index = cursor.getColumnIndex(UpdateEntry.COLUMN_NAME_VERSION);
|
||||||
|
update.setVersion(cursor.getString(index));
|
||||||
|
index = cursor.getColumnIndex(UpdateEntry.COLUMN_NAME_STATUS);
|
||||||
|
update.setPersistentStatus(cursor.getInt(index));
|
||||||
|
index = cursor.getColumnIndex(UpdateEntry.COLUMN_NAME_SIZE);
|
||||||
|
update.setFileSize(cursor.getLong(index));
|
||||||
|
updates.add(update);
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
return updates;
|
||||||
|
}
|
||||||
|
}
|
192
src/org/lineageos/updater/UpdatesListAdapter.java
Normal file
192
src/org/lineageos/updater/UpdatesListAdapter.java
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 The LineageOS Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.lineageos.updater;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.lineageos.updater.misc.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class UpdatesListAdapter extends RecyclerView.Adapter<UpdatesListAdapter.ViewHolder> {
|
||||||
|
|
||||||
|
private static final String TAG = "UpdateListAdapter";
|
||||||
|
|
||||||
|
private List<String> mDownloadIds;
|
||||||
|
private DownloadControllerInt mDownloadController;
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
|
private enum Action {
|
||||||
|
DOWNLOAD,
|
||||||
|
PAUSE,
|
||||||
|
RESUME,
|
||||||
|
CANCEL,
|
||||||
|
INSTALL
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
private TextView mName;
|
||||||
|
private Button mButton1;
|
||||||
|
private Button mButton2;
|
||||||
|
private ProgressBar mProgressBar;
|
||||||
|
|
||||||
|
public ViewHolder(final View view) {
|
||||||
|
super(view);
|
||||||
|
mName = (TextView) view.findViewById(R.id.name);
|
||||||
|
mButton1 = (Button) view.findViewById(R.id.button1);
|
||||||
|
mButton2 = (Button) view.findViewById(R.id.button2);
|
||||||
|
mProgressBar = (ProgressBar) view.findViewById(R.id.progress_bar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpdatesListAdapter(Context context) {
|
||||||
|
mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
|
||||||
|
View view = LayoutInflater.from(viewGroup.getContext())
|
||||||
|
.inflate(R.layout.update_item_view, viewGroup, false);
|
||||||
|
return new ViewHolder(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDownloadController(DownloadControllerInt downloadController) {
|
||||||
|
mDownloadController = downloadController;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(final ViewHolder viewHolder, int i) {
|
||||||
|
if (mDownloadController == null) {
|
||||||
|
viewHolder.mButton1.setEnabled(false);
|
||||||
|
viewHolder.mButton2.setEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String downloadId = mDownloadIds.get(i);
|
||||||
|
UpdateDownload update = mDownloadController.getUpdate(downloadId);
|
||||||
|
viewHolder.mName.setText(update.getName());
|
||||||
|
|
||||||
|
viewHolder.mProgressBar.setProgress(update.getProgress());
|
||||||
|
boolean indeterminate = update.getStatus() == UpdateStatus.STARTING ||
|
||||||
|
update.getStatus() == UpdateStatus.VERIFYING ||
|
||||||
|
update.getStatus() == UpdateStatus.INSTALLING;
|
||||||
|
viewHolder.mProgressBar.setIndeterminate(indeterminate);
|
||||||
|
|
||||||
|
if (mDownloadController.isDownloading(downloadId)) {
|
||||||
|
setButtonAction(viewHolder.mButton1, Action.PAUSE, downloadId, true);
|
||||||
|
viewHolder.mButton2.setEnabled(false);
|
||||||
|
} else {
|
||||||
|
// Allow one active download
|
||||||
|
boolean enabled = !mDownloadController.hasActiveDownloads();
|
||||||
|
int persistentStatus = update.getPersistentStatus();
|
||||||
|
if (persistentStatus == UpdateStatus.Persistent.INCOMPLETE) {
|
||||||
|
setButtonAction(viewHolder.mButton1, Action.RESUME, downloadId, enabled);
|
||||||
|
setButtonAction(viewHolder.mButton2, Action.CANCEL, downloadId, enabled);
|
||||||
|
} else if (persistentStatus == UpdateStatus.Persistent.VERIFIED) {
|
||||||
|
setButtonAction(viewHolder.mButton1, Action.INSTALL, downloadId, enabled);
|
||||||
|
setButtonAction(viewHolder.mButton2, Action.CANCEL, downloadId, enabled);
|
||||||
|
} else {
|
||||||
|
setButtonAction(viewHolder.mButton1, Action.DOWNLOAD, downloadId, enabled);
|
||||||
|
setButtonAction(viewHolder.mButton2, Action.CANCEL, downloadId, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return mDownloadIds == null ? 0 : mDownloadIds.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(List<String> downloadIds) {
|
||||||
|
mDownloadIds = downloadIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyItemChanged(String downloadId) {
|
||||||
|
notifyItemChanged(mDownloadIds.indexOf(downloadId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setButtonAction(Button button, Action action, final String downloadId,
|
||||||
|
boolean enabled) {
|
||||||
|
switch (action) {
|
||||||
|
case DOWNLOAD:
|
||||||
|
button.setText(R.string.download_button);
|
||||||
|
button.setEnabled(enabled);
|
||||||
|
button.setOnClickListener(!enabled ? null : new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
mDownloadController.startDownload(downloadId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case PAUSE:
|
||||||
|
button.setText(R.string.pause_button);
|
||||||
|
button.setEnabled(enabled);
|
||||||
|
button.setOnClickListener(!enabled ? null : new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
mDownloadController.pauseDownload(downloadId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case RESUME:
|
||||||
|
button.setText(R.string.resume_button);
|
||||||
|
button.setEnabled(enabled);
|
||||||
|
button.setOnClickListener(!enabled ? null : new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
mDownloadController.resumeDownload(downloadId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case CANCEL:
|
||||||
|
button.setText(R.string.cancel_button);
|
||||||
|
button.setEnabled(enabled);
|
||||||
|
button.setOnClickListener(!enabled ? null : new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
mDownloadController.cancelDownload(downloadId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case INSTALL:
|
||||||
|
button.setText(R.string.install_button);
|
||||||
|
button.setEnabled(enabled);
|
||||||
|
button.setOnClickListener(!enabled ? null : new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
try {
|
||||||
|
Utils.triggerUpdate(mContext,
|
||||||
|
mDownloadController.getUpdate(downloadId));
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Could not trigger the update", e);
|
||||||
|
// TODO: show error message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
131
src/org/lineageos/updater/misc/Utils.java
Normal file
131
src/org/lineageos/updater/misc/Utils.java
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 The LineageOS Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.lineageos.updater.misc;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.SystemProperties;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.lineageos.updater.R;
|
||||||
|
import org.lineageos.updater.Update;
|
||||||
|
import org.lineageos.updater.UpdateDownload;
|
||||||
|
import org.lineageos.updater.UpdateStatus;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Utils {
|
||||||
|
|
||||||
|
private static final String TAG = "Utils";
|
||||||
|
|
||||||
|
public static File getDownloadPath(Context context) {
|
||||||
|
boolean useCache = context.getResources().getBoolean(R.bool.download_in_cache);
|
||||||
|
int id = useCache ? R.string.download_path_cache : R.string.download_path_data;
|
||||||
|
return new File(context.getString(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File getCachedUpdateList(Context context) {
|
||||||
|
return new File(context.getCacheDir(), "updates.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should really return an Update object, but currently this only
|
||||||
|
// used to initialize UpdateDownload objects
|
||||||
|
private static UpdateDownload parseJsonUpdate(JSONObject object) throws JSONException {
|
||||||
|
UpdateDownload update = new UpdateDownload();
|
||||||
|
update.setTimestamp(object.getLong("datetime"));
|
||||||
|
update.setName(object.getString("filename"));
|
||||||
|
update.setDownloadId(object.getString("id"));
|
||||||
|
update.setType(object.getString("romtype"));
|
||||||
|
update.setDownloadUrl(object.getString("url"));
|
||||||
|
update.setVersion(object.getString("version"));
|
||||||
|
return update;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isCompatible(Update update) {
|
||||||
|
if (update.getTimestamp() < SystemProperties.getLong("ro.build.date.utc", 0)) {
|
||||||
|
Log.d(TAG, update.getName() + " is older than current build");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!update.getType().equalsIgnoreCase(SystemProperties.get("ro.cm.releasetype"))) {
|
||||||
|
Log.d(TAG, update.getName() + " has type " + update.getType());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!update.getVersion().equalsIgnoreCase(SystemProperties.get("ro.cm.build.version"))) {
|
||||||
|
Log.d(TAG, update.getName() + " has version " + update.getVersion());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<UpdateDownload> parseJson(File file, boolean compatibleOnly)
|
||||||
|
throws IOException, JSONException {
|
||||||
|
List<UpdateDownload> updates = new ArrayList<>();
|
||||||
|
|
||||||
|
String json = "";
|
||||||
|
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
|
||||||
|
for (String line; (line = br.readLine()) != null;) {
|
||||||
|
json += line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject obj = new JSONObject(json);
|
||||||
|
JSONArray updatesList = obj.getJSONArray("response");
|
||||||
|
for (int i = 0; i < updatesList.length(); i++) {
|
||||||
|
if (updatesList.isNull(i)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
UpdateDownload update = parseJsonUpdate(updatesList.getJSONObject(i));
|
||||||
|
if (compatibleOnly && isCompatible(update)) {
|
||||||
|
updates.add(update);
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Ignoring incompatible update " + update.getName());
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.e(TAG, "Could not parse update object, index=" + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getServerURL(Context context) {
|
||||||
|
String serverUrl = SystemProperties.get("cm.updater.uri");
|
||||||
|
if (serverUrl.trim().isEmpty()) {
|
||||||
|
serverUrl = context.getString(R.string.conf_update_server_url_def);
|
||||||
|
}
|
||||||
|
String incrementalVersion = SystemProperties.get("ro.build.version.incremental");
|
||||||
|
String device = SystemProperties.get("ro.cm.device").toLowerCase();
|
||||||
|
String type = SystemProperties.get("ro.cm.releasetype").toLowerCase();
|
||||||
|
return serverUrl + "/v1/" + device + "/" + type + "/" + incrementalVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void triggerUpdate(Context context, UpdateDownload update) throws IOException {
|
||||||
|
if (update.getStatus() == UpdateStatus.VERIFIED) {
|
||||||
|
android.os.RecoverySystem.installPackage(context, update.getFile());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user