diff options
author | Gabriele M <moto.falcon.git@gmail.com> | 2017-07-19 21:11:47 +0200 |
---|---|---|
committer | Gabriele M <moto.falcon.git@gmail.com> | 2017-07-20 00:45:34 +0200 |
commit | 1659fe26b9ca7f54f062fbe43b034b866a747dff (patch) | |
tree | 5fe5c0c353523c97ba6b80da7177033bae23d8b3 | |
parent | ee6013dfd1177a09be024cb0e86186f2873a2229 (diff) | |
download | android_packages_apps_Updater-1659fe26b9ca7f54f062fbe43b034b866a747dff.tar.gz android_packages_apps_Updater-1659fe26b9ca7f54f062fbe43b034b866a747dff.tar.bz2 android_packages_apps_Updater-1659fe26b9ca7f54f062fbe43b034b866a747dff.zip |
Allow to export verified updates
-rw-r--r-- | AndroidManifest.xml | 1 | ||||
-rw-r--r-- | res/drawable/ic_save.xml | 9 | ||||
-rw-r--r-- | res/menu/menu_action_mode.xml | 6 | ||||
-rw-r--r-- | res/values/strings.xml | 11 | ||||
-rw-r--r-- | src/org/lineageos/updater/UpdatesListAdapter.java | 28 | ||||
-rw-r--r-- | src/org/lineageos/updater/misc/FileUtils.java | 180 | ||||
-rw-r--r-- | src/org/lineageos/updater/misc/PermissionsUtils.java | 69 | ||||
-rw-r--r-- | src/org/lineageos/updater/misc/Utils.java | 12 |
8 files changed, 316 insertions, 0 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index e998849..c356d29 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -8,6 +8,7 @@ <uses-permission android:name="android.permission.REBOOT"/> <uses-permission android:name="android.permission.RECOVERY"/> <uses-permission android:name="android.permission.WAKE_LOCK" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <application android:allowBackup="false" diff --git a/res/drawable/ic_save.xml b/res/drawable/ic_save.xml new file mode 100644 index 0000000..a7a81a2 --- /dev/null +++ b/res/drawable/ic_save.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/> +</vector> diff --git a/res/menu/menu_action_mode.xml b/res/menu/menu_action_mode.xml index 4b27c3c..6b355e6 100644 --- a/res/menu/menu_action_mode.xml +++ b/res/menu/menu_action_mode.xml @@ -2,6 +2,12 @@ <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item + android:id="@+id/menu_export_update" + android:icon="@drawable/ic_save" + android:title="@string/menu_export_update" + android:visible="false" + app:showAsAction="ifRoom" /> + <item android:id="@+id/menu_delete_action" android:icon="@drawable/ic_delete" android:title="@string/menu_delete_update" diff --git a/res/values/strings.xml b/res/values/strings.xml index 25d62e1..94e844f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -22,6 +22,10 @@ WARNING: The application can and will delete any unknown file. --> <string name="download_path" translatable="false">/data/lineageos_updates/</string> + <!-- Directory where the downloads will be exported to. + The path is relative to the root of the external storage.--> + <string name="export_path" translatable="false">LineageOS updates/</string> + <string name="conf_update_server_url_def" translatable="false">https://download.lineageos.org/api</string> <string name="verification_failed_notification">Verification failed</string> @@ -51,6 +55,7 @@ <string name="menu_auto_updates_check">Auto updates check</string> <string name="menu_delete_update">Delete</string> <string name="menu_copy_url">Copy URL</string> + <string name="menu_export_update">Export update</string> <string name="dialog_checking_for_updates">Checking for updates</string> <string name="snack_updates_found">New updates found</string> @@ -89,6 +94,12 @@ <string name="label_download_url">Download URL</string> <string name="toast_download_url_copied">URL Copied</string> + <string name="snack_export_failed">Could not export download</string> + <string name="dialog_export_title">Exporting update</string> + <string name="dialog_export_message">Exporting update as <xliff:g id="filename">%1$s</xliff:g> into the external storage.</string> + <string name="notification_export_success">Update exported</string> + <string name="notification_export_fail">Export error</string> + <plurals name="duration_seconds"> <item quantity="one">1 second</item> <item quantity="other"><xliff:g id="count">%d</xliff:g> seconds</item> diff --git a/src/org/lineageos/updater/UpdatesListAdapter.java b/src/org/lineageos/updater/UpdatesListAdapter.java index 7f722c8..e3d0e1b 100644 --- a/src/org/lineageos/updater/UpdatesListAdapter.java +++ b/src/org/lineageos/updater/UpdatesListAdapter.java @@ -35,11 +35,14 @@ import android.widget.TextView; import org.lineageos.updater.controller.Controller; import org.lineageos.updater.misc.BuildInfoUtils; +import org.lineageos.updater.misc.FileUtils; +import org.lineageos.updater.misc.PermissionsUtils; import org.lineageos.updater.misc.StringGenerator; import org.lineageos.updater.misc.Utils; import org.lineageos.updater.model.UpdateDownload; import org.lineageos.updater.model.UpdateStatus; +import java.io.File; import java.io.IOException; import java.text.DateFormat; import java.text.NumberFormat; @@ -429,6 +432,8 @@ public class UpdatesListAdapter extends RecyclerView.Adapter<UpdatesListAdapter. inflater.inflate(R.menu.menu_action_mode, menu); menu.findItem(R.id.menu_delete_action).setVisible(canDelete); menu.findItem(R.id.menu_copy_url).setVisible(update.getAvailableOnline()); + menu.findItem(R.id.menu_export_update).setVisible( + update.getPersistentStatus() == UpdateStatus.Persistent.VERIFIED); return true; } @@ -457,6 +462,14 @@ public class UpdatesListAdapter extends RecyclerView.Adapter<UpdatesListAdapter. mActivity.getString(R.string.toast_download_url_copied)); mode.finish(); return true; + case R.id.menu_export_update: + // TODO: start exporting once the permission has been granted + boolean hasPermission = PermissionsUtils.checkAndRequestStoragePermission( + mActivity, 0); + if (hasPermission) { + exportUpdate(update); + } + return true; } return false; } @@ -473,4 +486,19 @@ public class UpdatesListAdapter extends RecyclerView.Adapter<UpdatesListAdapter. } }); } + + private void exportUpdate(UpdateDownload update) { + try { + File dest = new File(Utils.getExportPath(), update.getName()); + if (dest.exists()) { + dest = Utils.appendSequentialNumber(dest); + } + FileUtils.copyFileWithDialog(mActivity, update.getFile(), dest); + } catch (IOException e) { + Log.e(TAG, "Export failed", e); + mActivity.showSnackbar(R.string.snack_export_failed, + Snackbar.LENGTH_LONG); + } + stopActionMode(); + } } diff --git a/src/org/lineageos/updater/misc/FileUtils.java b/src/org/lineageos/updater/misc/FileUtils.java new file mode 100644 index 0000000..c6f7f44 --- /dev/null +++ b/src/org/lineageos/updater/misc/FileUtils.java @@ -0,0 +1,180 @@ +/* + * 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.app.NotificationManager; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.AsyncTask; +import android.support.v7.app.NotificationCompat; +import android.util.Log; + +import org.lineageos.updater.R; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; + +public class FileUtils { + + private static final String TAG = "FileUtils"; + + interface ProgressCallBack { + void update(int progress); + } + + private static class CallbackByteChannel implements ReadableByteChannel { + private ProgressCallBack mCallback; + private long mSize; + private ReadableByteChannel mReadableByteChannel; + private long mSizeRead; + private int mProgress; + + private CallbackByteChannel(ReadableByteChannel readableByteChannel, long expectedSize, + ProgressCallBack callback) { + this.mCallback = callback; + this.mSize = expectedSize; + this.mReadableByteChannel = readableByteChannel; + } + + @Override + public void close() throws IOException { + mReadableByteChannel.close(); + } + + @Override + public boolean isOpen() { + return mReadableByteChannel.isOpen(); + } + + @Override + public int read(ByteBuffer bb) throws IOException { + int read; + if ((read = mReadableByteChannel.read(bb)) > 0) { + mSizeRead += read; + int progress = mSize > 0 ? Math.round(mSizeRead * 100.f / mSize) : -1; + if (mProgress != progress) { + mCallback.update(progress); + mProgress = progress; + } + } + return read; + } + } + + public static void copyFile(File sourceFile, File destFile, ProgressCallBack progressCallBack) + throws IOException { + try (FileChannel sourceChannel = new FileInputStream(sourceFile).getChannel(); + FileChannel destChannel = new FileOutputStream(destFile).getChannel()) { + if (progressCallBack != null) { + ReadableByteChannel readableByteChannel = new CallbackByteChannel(sourceChannel, + sourceFile.length(), progressCallBack); + destChannel.transferFrom(readableByteChannel, 0, sourceChannel.size()); + } else { + destChannel.transferFrom(sourceChannel, 0, sourceChannel.size()); + } + } catch (IOException e) { + Log.e(TAG, "Could not copy file", e); + if (destFile.exists()) { + destFile.delete(); + } + throw e; + } + } + + public static void copyFile(File sourceFile, File destFile) throws IOException { + copyFile(sourceFile, destFile, null); + } + + public static void copyFileWithDialog(Context context, File sourceFile, File destFile) + throws IOException { + + final int NOTIFICATION_ID = 11; + + new AsyncTask<Void, String, Boolean>() { + + private ProgressDialog mProgressDialog; + private boolean mCancelled; + private ProgressCallBack mProgressCallBack; + + @Override + protected void onPreExecute() { + super.onPreExecute(); + mProgressDialog = new ProgressDialog(context); + mProgressDialog.setTitle(context.getString(R.string.dialog_export_title)); + mProgressDialog.setMessage( + context.getString(R.string.dialog_export_message, destFile.getName())); + mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + mProgressDialog.setCancelable(true); + mProgressDialog.setProgressNumberFormat(null); + mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + cancel(true); + } + }); + mProgressDialog.setCanceledOnTouchOutside(false); + mProgressCallBack = new ProgressCallBack() { + @Override + public void update(int progress) { + mProgressDialog.setProgress(progress); + } + }; + mProgressDialog.show(); + } + + @Override + protected Boolean doInBackground(Void... voids) { + try { + copyFile(sourceFile, destFile, mProgressCallBack); + return true; + } catch (IOException e) { + return false; + } + } + + @Override + protected void onCancelled() { + mCancelled = true; + destFile.delete(); + } + + @Override + protected void onPostExecute(Boolean success) { + mProgressDialog.dismiss(); + if (mCancelled) { + destFile.delete(); + } else { + NotificationManager nm = (NotificationManager) context.getSystemService( + Context.NOTIFICATION_SERVICE); + NotificationCompat.Builder builder = new NotificationCompat.Builder(context); + builder.setSmallIcon(R.drawable.ic_system_update); + builder.setContentTitle( + success ? context.getString(R.string.notification_export_success) + : context.getString(R.string.notification_export_fail)); + builder.setContentText(destFile.getName()); + final String notificationTag = destFile.getAbsolutePath(); + nm.notify(notificationTag, NOTIFICATION_ID, builder.build()); + } + } + }.execute(); + } +} diff --git a/src/org/lineageos/updater/misc/PermissionsUtils.java b/src/org/lineageos/updater/misc/PermissionsUtils.java new file mode 100644 index 0000000..aeaf0dc --- /dev/null +++ b/src/org/lineageos/updater/misc/PermissionsUtils.java @@ -0,0 +1,69 @@ +/* + * 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.Manifest; +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageManager; + +import java.util.ArrayList; +import java.util.List; + +public class PermissionsUtils { + + public static boolean hasPermission(Context context, String permission) { + int permissionState = context.checkSelfPermission(permission); + return permissionState == PackageManager.PERMISSION_GRANTED; + } + + /** + * Check the given permissions and requests them if needed. + * + * @param activity The target activity + * @param permissions The permissions to check + * @param requestCode @see Activity#requestPermissions(String[] , int) + * @return true if the permission is granted, false otherwise + */ + public static boolean checkAndRequestPermissions(final Activity activity, + final String[] permissions, final int requestCode) { + List<String> permissionsList = new ArrayList<>(); + for (String permission : permissions) { + if (!hasPermission(activity, permission)) { + permissionsList.add(permission); + } + } + if (permissionsList.size() == 0) { + return true; + } else { + String[] permissionsArray = new String[permissionsList.size()]; + permissionsArray = permissionsList.toArray(permissionsArray); + activity.requestPermissions(permissionsArray, requestCode); + return false; + } + } + + /** + * Check and request the write external storage permission + * + * @see #checkAndRequestPermissions(Activity, String[], int) + */ + public static boolean checkAndRequestStoragePermission(Activity activity, int requestCode) { + return checkAndRequestPermissions(activity, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + requestCode); + } +} diff --git a/src/org/lineageos/updater/misc/Utils.java b/src/org/lineageos/updater/misc/Utils.java index 35fd739..5f7849f 100644 --- a/src/org/lineageos/updater/misc/Utils.java +++ b/src/org/lineageos/updater/misc/Utils.java @@ -23,6 +23,7 @@ import android.content.pm.PackageManager; import android.content.SharedPreferences; import android.net.ConnectivityManager; import android.net.NetworkInfo; +import android.os.Environment; import android.os.SystemProperties; import android.preference.PreferenceManager; import android.util.Log; @@ -60,6 +61,17 @@ public class Utils { return new File(context.getString(R.string.download_path)); } + public static File getExportPath(Context context) { + File dir = new File(Environment.getExternalStorageDirectory(), + context.getString(R.string.export_path)); + if (!dir.isDirectory()) { + if (dir.exists() || !dir.mkdirs()) { + throw new RuntimeException("Could not create directory"); + } + } + return dir; + } + public static File getCachedUpdateList(Context context) { return new File(context.getCacheDir(), "updates.json"); } |