From 4eb73602d28358c3c8b229df7a9dad3642ca80ee Mon Sep 17 00:00:00 2001 From: Chirag Maheshwari Date: Tue, 15 Aug 2017 04:18:54 +0530 Subject: [PATCH] Auto upload feature (#253) * adds auto upload settings to preference screen * adds NEW_PICTURE broadcast receiver * adds upload service * adds upload settings preference fragment * adds hda and share list fetching to upload settings * adds sqlite db for storing image paths * adds custom upload queue * implements upload manager and server connection in background service * fix the UI stuck on file uploads * fix multiple files uploading at the same time * code refactoring * adds upload only on wifi preference option * adds api-19 to travis.yml * adds Job for tracking changes in images content uri * adds job scheduling to the application class * adds net connnectivity job * adds permission checker to auto upload settings --- src/main/AndroidManifest.xml | 19 + .../org/amahi/anywhere/AmahiApplication.java | 28 +- .../java/org/amahi/anywhere/AmahiModule.java | 8 +- .../activity/ServerFilesActivity.java | 18 +- .../anywhere/activity/SettingsActivity.java | 37 +- .../bus/ServerFileUploadCompleteEvent.java | 20 +- .../bus/ServerFileUploadProgressEvent.java | 20 +- .../bus/UploadSettingsOpeningEvent.java | 23 + .../org/amahi/anywhere/db/UploadQueueDb.java | 44 ++ .../anywhere/db/UploadQueueDbHelper.java | 94 ++++ .../fragment/ServerFilesFragment.java | 8 - .../anywhere/fragment/SettingsFragment.java | 74 ++-- .../fragment/UploadSettingsFragment.java | 419 ++++++++++++++++++ .../anywhere/job/NetConnectivityJob.java | 91 ++++ .../amahi/anywhere/job/PhotosContentJob.java | 128 ++++++ .../org/amahi/anywhere/model/UploadFile.java | 38 ++ .../anywhere/receiver/CameraReceiver.java | 43 ++ .../anywhere/receiver/NetworkReceiver.java | 31 +- .../anywhere/server/client/ServerClient.java | 45 +- .../amahi/anywhere/server/model/Server.java | 4 + .../response/ServerFileUploadResponse.java | 30 +- .../amahi/anywhere/service/UploadService.java | 313 +++++++++++++ .../java/org/amahi/anywhere/util/Intents.java | 14 + .../org/amahi/anywhere/util/NetworkUtils.java | 64 +++ .../anywhere/util/ProgressRequestBody.java | 121 ++--- .../amahi/anywhere/util/UploadManager.java | 158 +++++++ src/main/res/values/preferences.xml | 15 + src/main/res/values/strings.xml | 4 + src/main/res/xml/settings.xml | 16 +- src/main/res/xml/upload_settings.xml | 49 ++ 30 files changed, 1814 insertions(+), 162 deletions(-) create mode 100644 src/main/java/org/amahi/anywhere/bus/UploadSettingsOpeningEvent.java create mode 100644 src/main/java/org/amahi/anywhere/db/UploadQueueDb.java create mode 100644 src/main/java/org/amahi/anywhere/db/UploadQueueDbHelper.java create mode 100644 src/main/java/org/amahi/anywhere/fragment/UploadSettingsFragment.java create mode 100644 src/main/java/org/amahi/anywhere/job/NetConnectivityJob.java create mode 100644 src/main/java/org/amahi/anywhere/job/PhotosContentJob.java create mode 100644 src/main/java/org/amahi/anywhere/model/UploadFile.java create mode 100644 src/main/java/org/amahi/anywhere/receiver/CameraReceiver.java create mode 100644 src/main/java/org/amahi/anywhere/service/UploadService.java create mode 100644 src/main/java/org/amahi/anywhere/util/NetworkUtils.java create mode 100644 src/main/java/org/amahi/anywhere/util/UploadManager.java create mode 100644 src/main/res/xml/upload_settings.xml diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index af0f9124c..09885322c 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -147,6 +147,7 @@ + @@ -166,6 +167,24 @@ + + + + + + + + + + + + diff --git a/src/main/java/org/amahi/anywhere/AmahiApplication.java b/src/main/java/org/amahi/anywhere/AmahiApplication.java index 6ce1a3243..2b86b62db 100644 --- a/src/main/java/org/amahi/anywhere/AmahiApplication.java +++ b/src/main/java/org/amahi/anywhere/AmahiApplication.java @@ -21,10 +21,15 @@ import android.app.Application; import android.content.Context; +import android.os.Build; import android.os.StrictMode; +import android.support.annotation.RequiresApi; import com.crashlytics.android.Crashlytics; +import org.amahi.anywhere.job.NetConnectivityJob; +import org.amahi.anywhere.job.PhotosContentJob; + import dagger.ObjectGraph; import io.fabric.sdk.android.Fabric; import timber.log.Timber; @@ -49,9 +54,13 @@ public void onCreate() { setUpDetecting(); setUpInjections(); - } - private void setUpLogging() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + setUpJobs(); + } + } + + private void setUpLogging() { if (isDebugging()) { Timber.plant(new Timber.DebugTree()); } @@ -80,4 +89,19 @@ private void setUpInjections() { public void inject(Object injectionsConsumer) { injector.inject(injectionsConsumer); } + + public static class JobIds { + public static final int PHOTOS_CONTENT_JOB = 125; + public static final int NET_CONNECTIVITY_JOB = 126; + } + + @RequiresApi(api = Build.VERSION_CODES.N) + private void setUpJobs() { + if (!PhotosContentJob.isScheduled(this)) { + PhotosContentJob.scheduleJob(this); + } + if (!NetConnectivityJob.isScheduled(this)) { + NetConnectivityJob.scheduleJob(this); + } + } } diff --git a/src/main/java/org/amahi/anywhere/AmahiModule.java b/src/main/java/org/amahi/anywhere/AmahiModule.java index 90d40f05b..2d9b94820 100644 --- a/src/main/java/org/amahi/anywhere/AmahiModule.java +++ b/src/main/java/org/amahi/anywhere/AmahiModule.java @@ -38,8 +38,10 @@ import org.amahi.anywhere.fragment.ServerFilesFragment; import org.amahi.anywhere.fragment.ServerSharesFragment; import org.amahi.anywhere.fragment.SettingsFragment; +import org.amahi.anywhere.fragment.UploadSettingsFragment; import org.amahi.anywhere.server.ApiModule; import org.amahi.anywhere.service.AudioService; +import org.amahi.anywhere.service.UploadService; import org.amahi.anywhere.service.VideoService; import org.amahi.anywhere.tv.activity.TVWebViewActivity; import org.amahi.anywhere.tv.activity.TvPlaybackOverlayActivity; @@ -47,6 +49,7 @@ import org.amahi.anywhere.tv.fragment.ServerFileTvFragment; import org.amahi.anywhere.tv.fragment.ServerSelectFragment; import org.amahi.anywhere.tv.fragment.TvPlaybackOverlayFragment; +import org.amahi.anywhere.util.UploadManager; import javax.inject.Singleton; @@ -78,13 +81,16 @@ ServerFileImageFragment.class, ServerFileDownloadingFragment.class, SettingsFragment.class, + UploadSettingsFragment.class, AudioService.class, VideoService.class, MainTVFragment.class, TVWebViewActivity.class, ServerFileTvFragment.class, TvPlaybackOverlayFragment.class, - TvPlaybackOverlayActivity.class + TvPlaybackOverlayActivity.class, + UploadService.class, + UploadManager.class } ) class AmahiModule { diff --git a/src/main/java/org/amahi/anywhere/activity/ServerFilesActivity.java b/src/main/java/org/amahi/anywhere/activity/ServerFilesActivity.java index eab8f3f59..2fa98278d 100644 --- a/src/main/java/org/amahi/anywhere/activity/ServerFilesActivity.java +++ b/src/main/java/org/amahi/anywhere/activity/ServerFilesActivity.java @@ -408,13 +408,15 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { String filePath = querySelectedImagePath(selectedImageUri); if (filePath != null) { File file = new File(filePath); - ServerFilesFragment fragment = (ServerFilesFragment) - getSupportFragmentManager() - .findFragmentById(R.id.container_files); - if (fragment.checkForDuplicateFile(file.getName())) { - showDuplicateFileUploadDialog(file); - } else { - uploadFile(file); + if (file.exists()) { + ServerFilesFragment fragment = (ServerFilesFragment) + getSupportFragmentManager() + .findFragmentById(R.id.container_files); + if (fragment.checkForDuplicateFile(file.getName())) { + showDuplicateFileUploadDialog(file); + } else { + uploadFile(file); + } } } } @@ -461,7 +463,7 @@ public void onClick(DialogInterface dialog, int which) { } private void uploadFile(File uploadFile) { - serverClient.uploadFile(uploadFile, getShare(), file); + serverClient.uploadFile(0, uploadFile, getShare(), file); uploadProgressDialog.show(); } diff --git a/src/main/java/org/amahi/anywhere/activity/SettingsActivity.java b/src/main/java/org/amahi/anywhere/activity/SettingsActivity.java index 75980cffb..1139b1774 100644 --- a/src/main/java/org/amahi/anywhere/activity/SettingsActivity.java +++ b/src/main/java/org/amahi/anywhere/activity/SettingsActivity.java @@ -19,21 +19,25 @@ package org.amahi.anywhere.activity; -import android.os.Bundle; import android.app.FragmentManager; import android.app.FragmentTransaction; +import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.MenuItem; +import com.squareup.otto.Subscribe; + import org.amahi.anywhere.R; +import org.amahi.anywhere.bus.BusProvider; +import org.amahi.anywhere.bus.UploadSettingsOpeningEvent; import org.amahi.anywhere.fragment.SettingsFragment; +import org.amahi.anywhere.fragment.UploadSettingsFragment; /** * Settings activity. Shows application's settings. * Settings itself are provided via {@link org.amahi.anywhere.fragment.SettingsFragment}. */ -public class SettingsActivity extends AppCompatActivity -{ +public class SettingsActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -53,18 +57,41 @@ private void setUpHomeNavigation() { private void setUpSettingsFragment() { FragmentManager fragmentManager = getFragmentManager(); fragmentManager.beginTransaction().setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) - .replace(R.id.settings_container,new SettingsFragment()).commit(); + .replace(R.id.settings_container, new SettingsFragment()).commit(); } @Override public boolean onOptionsItemSelected(MenuItem menuItem) { switch (menuItem.getItemId()) { case android.R.id.home: - finish(); + onBackPressed(); return true; default: return super.onOptionsItemSelected(menuItem); } } + + @Subscribe + public void onUploadSettingsOpenEvent(UploadSettingsOpeningEvent event) { + getFragmentManager().beginTransaction() + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) + .replace(R.id.settings_container, new UploadSettingsFragment()) + .addToBackStack(null) + .commit(); + } + + @Override + protected void onResume() { + super.onResume(); + + BusProvider.getBus().register(this); + } + + @Override + protected void onPause() { + super.onPause(); + + BusProvider.getBus().unregister(this); + } } diff --git a/src/main/java/org/amahi/anywhere/bus/ServerFileUploadCompleteEvent.java b/src/main/java/org/amahi/anywhere/bus/ServerFileUploadCompleteEvent.java index 2e45052d3..23f934743 100644 --- a/src/main/java/org/amahi/anywhere/bus/ServerFileUploadCompleteEvent.java +++ b/src/main/java/org/amahi/anywhere/bus/ServerFileUploadCompleteEvent.java @@ -20,13 +20,19 @@ package org.amahi.anywhere.bus; public class ServerFileUploadCompleteEvent implements BusEvent { - private boolean wasUploadSuccessful; + private int id; + private boolean wasUploadSuccessful; - public ServerFileUploadCompleteEvent(boolean wasUploadSuccessful) { - this.wasUploadSuccessful = wasUploadSuccessful; - } + public ServerFileUploadCompleteEvent(int id, boolean wasUploadSuccessful) { + this.id = id; + this.wasUploadSuccessful = wasUploadSuccessful; + } - public boolean wasUploadSuccessful() { - return wasUploadSuccessful; - } + public int getId() { + return id; + } + + public boolean wasUploadSuccessful() { + return wasUploadSuccessful; + } } diff --git a/src/main/java/org/amahi/anywhere/bus/ServerFileUploadProgressEvent.java b/src/main/java/org/amahi/anywhere/bus/ServerFileUploadProgressEvent.java index c57391fe6..d183935c1 100644 --- a/src/main/java/org/amahi/anywhere/bus/ServerFileUploadProgressEvent.java +++ b/src/main/java/org/amahi/anywhere/bus/ServerFileUploadProgressEvent.java @@ -20,13 +20,19 @@ package org.amahi.anywhere.bus; public class ServerFileUploadProgressEvent implements BusEvent { - private int progress; + private int id; + private int progress; - public ServerFileUploadProgressEvent(int progress) { - this.progress = progress; - } + public ServerFileUploadProgressEvent(int id, int progress) { + this.id = id; + this.progress = progress; + } - public int getProgress() { - return this.progress; - } + public int getId() { + return this.id; + } + + public int getProgress() { + return this.progress; + } } diff --git a/src/main/java/org/amahi/anywhere/bus/UploadSettingsOpeningEvent.java b/src/main/java/org/amahi/anywhere/bus/UploadSettingsOpeningEvent.java new file mode 100644 index 000000000..43ebb29a9 --- /dev/null +++ b/src/main/java/org/amahi/anywhere/bus/UploadSettingsOpeningEvent.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2014 Amahi + * + * This file is part of Amahi. + * + * Amahi is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Amahi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Amahi. If not, see . + */ + +package org.amahi.anywhere.bus; + +public class UploadSettingsOpeningEvent implements BusEvent { +} diff --git a/src/main/java/org/amahi/anywhere/db/UploadQueueDb.java b/src/main/java/org/amahi/anywhere/db/UploadQueueDb.java new file mode 100644 index 000000000..db3246544 --- /dev/null +++ b/src/main/java/org/amahi/anywhere/db/UploadQueueDb.java @@ -0,0 +1,44 @@ +package org.amahi.anywhere.db; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +/** + * SQLite db for maintaining image uploads in a persistent database. + * Query methods managed by {@link UploadQueueDbHelper UploadQueueDbHelper}. + */ + +class UploadQueueDb extends SQLiteOpenHelper { + + // Database version + private static final int DATABASE_VERSION = 1; + + // Database Name + private static final String DATABASE_NAME = "AMAHI_ANYWHERE_DATABASE"; + + // Table name + static final String TABLE_NAME = "UPLOAD_QUEUE_TABLE"; + + // column names + static final String KEY_ID = "id"; + static final String KEY_FILE_PATH = "file_path"; + + UploadQueueDb(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + + KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + KEY_FILE_PATH + " VARCHAR(200) NOT NULL)"; + + db.execSQL(CREATE_TABLE); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + } + +} diff --git a/src/main/java/org/amahi/anywhere/db/UploadQueueDbHelper.java b/src/main/java/org/amahi/anywhere/db/UploadQueueDbHelper.java new file mode 100644 index 000000000..4fff60f8b --- /dev/null +++ b/src/main/java/org/amahi/anywhere/db/UploadQueueDbHelper.java @@ -0,0 +1,94 @@ +package org.amahi.anywhere.db; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import org.amahi.anywhere.model.UploadFile; + +import java.util.ArrayList; + +import static org.amahi.anywhere.db.UploadQueueDb.KEY_ID; +import static org.amahi.anywhere.db.UploadQueueDb.TABLE_NAME; + +/** + * Performs CRUD operation on SQLite db provided by {@link UploadQueueDb UploadQueueDb}. + */ + +public class UploadQueueDbHelper { + + private UploadQueueDb uploadQueueDb; + private SQLiteDatabase sqLiteDatabase; + private static UploadQueueDbHelper uploadQueueDbHelper; + + public static UploadQueueDbHelper init(Context context) { + if (uploadQueueDbHelper == null) uploadQueueDbHelper = new UploadQueueDbHelper(context); + return uploadQueueDbHelper; + } + + private UploadQueueDbHelper(Context context) { + uploadQueueDb = new UploadQueueDb(context); + sqLiteDatabase = uploadQueueDb.getWritableDatabase(); + } + + public UploadFile addNewImagePath(String imagePath) { + ContentValues values = new ContentValues(); + + values.put(UploadQueueDb.KEY_FILE_PATH, imagePath); + int id = (int) sqLiteDatabase.insert(TABLE_NAME, null, values); + if (id != -1) { + return new UploadFile(id, imagePath); + } else { + return null; + } + } + + public ArrayList getAllImagePaths() { + ArrayList imagePaths = new ArrayList<>(); + + Cursor cursor = sqLiteDatabase.query(TABLE_NAME, null, null, null, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + while (!cursor.isAfterLast()) { + int id = cursor.getInt( + cursor.getColumnIndex(UploadQueueDb.KEY_ID)); + String imagePath = cursor.getString( + cursor.getColumnIndex(UploadQueueDb.KEY_FILE_PATH)); + + UploadFile uploadFile = new UploadFile(id, imagePath); + imagePaths.add(uploadFile); + cursor.moveToNext(); + } + } + if (cursor != null) { + cursor.close(); + } + + return imagePaths; + } + + public void removeFirstImagePath() { + Cursor cursor = sqLiteDatabase.query(TABLE_NAME, null, null, null, null, null, null); + if (cursor.moveToFirst()) { + String rowId = cursor.getString(cursor.getColumnIndex(KEY_ID)); + + sqLiteDatabase.delete(TABLE_NAME, KEY_ID + "=?", new String[]{rowId}); + } + cursor.close(); + } + + public void removeImagePath(int id) { + sqLiteDatabase.delete(TABLE_NAME, KEY_ID + "=?", new String[]{String.valueOf(id)}); + } + + public void clearDb() { + sqLiteDatabase.execSQL("DELETE FROM " + TABLE_NAME); + } + + public void closeDataBase() { + sqLiteDatabase.close(); + uploadQueueDb.close(); + uploadQueueDbHelper = null; + } + +} diff --git a/src/main/java/org/amahi/anywhere/fragment/ServerFilesFragment.java b/src/main/java/org/amahi/anywhere/fragment/ServerFilesFragment.java index 2b5e944d7..b68fdbb87 100644 --- a/src/main/java/org/amahi/anywhere/fragment/ServerFilesFragment.java +++ b/src/main/java/org/amahi/anywhere/fragment/ServerFilesFragment.java @@ -705,14 +705,6 @@ public boolean checkForDuplicateFile(String fileName) { return false; } - public void refreshFileList() { - if (!isMetadataAvailable()) { - getFilesAdapter().notifyDataSetChanged(); - } else { - getFilesAdapter().notifyDataSetChanged(); - } - } - @Override public void onResume() { super.onResume(); diff --git a/src/main/java/org/amahi/anywhere/fragment/SettingsFragment.java b/src/main/java/org/amahi/anywhere/fragment/SettingsFragment.java index 236308d78..321a353a4 100644 --- a/src/main/java/org/amahi/anywhere/fragment/SettingsFragment.java +++ b/src/main/java/org/amahi/anywhere/fragment/SettingsFragment.java @@ -29,6 +29,7 @@ import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceFragment; +import android.preference.PreferenceManager; import android.support.annotation.Nullable; import android.support.design.widget.Snackbar; import android.widget.Toast; @@ -37,6 +38,8 @@ import org.amahi.anywhere.R; import org.amahi.anywhere.account.AmahiAccount; import org.amahi.anywhere.activity.NavigationActivity; +import org.amahi.anywhere.bus.BusProvider; +import org.amahi.anywhere.bus.UploadSettingsOpeningEvent; import org.amahi.anywhere.server.ApiConnection; import org.amahi.anywhere.server.client.ServerClient; import org.amahi.anywhere.util.Android; @@ -51,9 +54,8 @@ * Settings fragment. Shows application's settings. */ public class SettingsFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener, - SharedPreferences.OnSharedPreferenceChangeListener, - AccountManagerCallback -{ + SharedPreferences.OnSharedPreferenceChangeListener, + AccountManagerCallback { @Inject ServerClient serverClient; @@ -68,6 +70,10 @@ private void setUpInjections() { AmahiApplication.from(getActivity()).inject(this); } + private void setUpTitle() { + getActivity().setTitle(R.string.title_settings); + } + private void setUpSettings() { setUpSettingsContent(); setUpSettingsSummary(); @@ -81,9 +87,11 @@ private void setUpSettingsContent() { private void setUpSettingsSummary() { ListPreference serverConnection = (ListPreference) getPreference(R.string.preference_key_server_connection); Preference applicationVersion = getPreference(R.string.preference_key_about_version); + Preference autoUpload = getPreference(R.string.preference_screen_key_upload); serverConnection.setSummary(getServerConnectionSummary()); applicationVersion.setSummary(getApplicationVersionSummary()); + autoUpload.setSummary(getAutoUploadSummary()); } private String getServerConnectionSummary() { @@ -92,14 +100,24 @@ private String getServerConnectionSummary() { return String.format("%s", serverConnection.getEntry()); } - private Preference getPreference(int id){ + private Preference getPreference(int id) { return findPreference(getString(id)); } private String getApplicationVersionSummary() { return String.format( - "Amahi for Android %s\nwww.amahi.org/android", - Android.getApplicationVersion()); + "Amahi for Android %s\nwww.amahi.org/android", + Android.getApplicationVersion()); + } + + private String getAutoUploadSummary() { + return isUploadEnabled() ? "Enabled" : "Disabled"; + } + + private boolean isUploadEnabled() { + PreferenceManager preferenceManager = getPreferenceManager(); + return preferenceManager.getSharedPreferences() + .getBoolean(getString(R.string.preference_key_upload_switch), false); } private void setUpSettingsListeners() { @@ -108,39 +126,38 @@ private void setUpSettingsListeners() { Preference applicationFeedback = getPreference(R.string.preference_key_about_feedback); Preference applicationRating = getPreference(R.string.preference_key_about_rating); Preference shareApp = getPreference(R.string.preference_key_tell_a_friend); + Preference autoUpload = getPreference(R.string.preference_screen_key_upload); accountSignOut.setOnPreferenceClickListener(this); applicationVersion.setOnPreferenceClickListener(this); applicationFeedback.setOnPreferenceClickListener(this); applicationRating.setOnPreferenceClickListener(this); shareApp.setOnPreferenceClickListener(this); + autoUpload.setOnPreferenceClickListener(this); } @Override public boolean onPreferenceClick(Preference preference) { if (preference.getKey().equals(getString(R.string.preference_key_account_sign_out))) { tearDownAccount(); - } - - if (preference.getKey().equals(getString(R.string.preference_key_about_version))) { + } else if (preference.getKey().equals(getString(R.string.preference_key_about_version))) { setUpApplicationVersion(); - } - - if (preference.getKey().equals(getString(R.string.preference_key_about_feedback))) { + } else if (preference.getKey().equals(getString(R.string.preference_key_about_feedback))) { setUpApplicationFeedback(); - } - - if (preference.getKey().equals(getString(R.string.preference_key_about_rating))) { + } else if (preference.getKey().equals(getString(R.string.preference_key_about_rating))) { setUpApplicationRating(); - } - - if (preference.getKey().equals(getString(R.string.preference_key_tell_a_friend))){ + } else if (preference.getKey().equals(getString(R.string.preference_key_tell_a_friend))) { sharedIntent(); + } else if (preference.getKey().equals(getString(R.string.preference_screen_key_upload))) { + openUploadSettingsFragment(); } - return true; } + private void openUploadSettingsFragment() { + BusProvider.getBus().post(new UploadSettingsOpeningEvent()); + } + private void tearDownAccount() { if (!getAccounts().isEmpty()) { Account account = getAccounts().get(0); @@ -172,13 +189,13 @@ private void tearDownActivity() { getActivity().finish(); } - private void sharedIntent(){ + private void sharedIntent() { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); - sendIntent.putExtra(Intent.EXTRA_SUBJECT,getString(R.string.share_subject)); - sendIntent.putExtra(Intent.EXTRA_TEXT,getString(R.string.share_message)); + sendIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_subject)); + sendIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.share_message)); sendIntent.setType("text/plain"); - startActivity(Intent.createChooser(sendIntent,getString(R.string.share_screen_title))); + startActivity(Intent.createChooser(sendIntent, getString(R.string.share_screen_title))); } private void setUpApplicationVersion() { @@ -190,9 +207,8 @@ private void setUpApplicationFeedback() { Intent intent = Intents.Builder.with(getActivity()).buildFeedbackIntent(); if (intent.resolveActivity(getActivity().getPackageManager()) != null) { startActivity(intent); - } - else { - Snackbar.make(getView(),getString(R.string.application_not_found),Snackbar.LENGTH_SHORT).show(); + } else { + Snackbar.make(getView(), getString(R.string.application_not_found), Snackbar.LENGTH_SHORT).show(); } } @@ -200,9 +216,8 @@ private void setUpApplicationRating() { Intent intent = Intents.Builder.with(getActivity()).buildGooglePlayIntent(); if (intent.resolveActivity(getActivity().getPackageManager()) != null) { startActivity(intent); - } - else { - Snackbar.make(getView(),getString(R.string.application_not_found),Snackbar.LENGTH_SHORT).show(); + } else { + Snackbar.make(getView(), getString(R.string.application_not_found), Snackbar.LENGTH_SHORT).show(); } } @@ -261,6 +276,7 @@ public void onResume() { super.onResume(); setUpSettingsPreferenceListener(); + setUpTitle(); } private void setUpSettingsPreferenceListener() { diff --git a/src/main/java/org/amahi/anywhere/fragment/UploadSettingsFragment.java b/src/main/java/org/amahi/anywhere/fragment/UploadSettingsFragment.java new file mode 100644 index 000000000..8c15236ce --- /dev/null +++ b/src/main/java/org/amahi/anywhere/fragment/UploadSettingsFragment.java @@ -0,0 +1,419 @@ +/* + * Copyright (c) 2015 Amahi + * + * This file is part of Amahi. + * + * Amahi is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Amahi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Amahi. If not, see . + */ + +package org.amahi.anywhere.fragment; + +import android.Manifest; +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerCallback; +import android.accounts.AccountManagerFuture; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +import android.content.SharedPreferences; +import android.os.Build; +import android.os.Bundle; +import android.preference.EditTextPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceFragment; +import android.preference.PreferenceManager; +import android.preference.SwitchPreference; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; +import android.support.design.widget.Snackbar; +import android.view.View; + +import com.squareup.otto.Subscribe; + +import org.amahi.anywhere.AmahiApplication; +import org.amahi.anywhere.R; +import org.amahi.anywhere.account.AmahiAccount; +import org.amahi.anywhere.activity.ServerFilesActivity; +import org.amahi.anywhere.bus.BusProvider; +import org.amahi.anywhere.bus.ServerConnectedEvent; +import org.amahi.anywhere.bus.ServerConnectionChangedEvent; +import org.amahi.anywhere.bus.ServerSharesLoadedEvent; +import org.amahi.anywhere.bus.ServersLoadedEvent; +import org.amahi.anywhere.server.client.AmahiClient; +import org.amahi.anywhere.server.client.ServerClient; +import org.amahi.anywhere.server.model.Server; +import org.amahi.anywhere.server.model.ServerShare; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.inject.Inject; + +import pub.devrel.easypermissions.AppSettingsDialog; +import pub.devrel.easypermissions.EasyPermissions; + +/** + * Upload Settings fragment. Shows upload settings. + */ +public class UploadSettingsFragment extends PreferenceFragment implements + Preference.OnPreferenceChangeListener, + AccountManagerCallback, + EasyPermissions.PermissionCallbacks { + + private static final int READ_PERMISSIONS = 110; + @Inject + AmahiClient amahiClient; + + @Inject + ServerClient serverClient; + + private String authenticationToken; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setUpInjections(); + + setUpTitle(); + + setUpSettings(); + } + + private void setUpInjections() { + AmahiApplication.from(getActivity()).inject(this); + } + + private void setUpTitle() { + getActivity().setTitle(R.string.preference_title_upload_settings); + } + + private void setUpSettings() { + setUpSettingsContent(); + setUpSettingsTitle(); + toggleUploadSettings(isUploadEnabled()); + setUpSettingsListeners(); + } + + private void setUpSettingsContent() { + addPreferencesFromResource(R.xml.upload_settings); + } + + private void setUpSettingsTitle() { + getAutoUploadSwitchPreference().setTitle(getAutoUploadTitle(isUploadEnabled())); + } + + private AccountManager getAccountManager() { + return AccountManager.get(getActivity()); + } + + private List getAccounts() { + return Arrays.asList(getAccountManager().getAccountsByType(AmahiAccount.TYPE)); + } + + private void setUpAuthenticationToken() { + if (authenticationToken != null) { + setUpServersContent(authenticationToken); + } else { + Account account = getAccounts().get(0); + getAccountManager().getAuthToken(account, AmahiAccount.TYPE, null, getActivity(), this, null); + } + + } + + @Override + public void run(AccountManagerFuture future) { + try { + Bundle accountManagerResult = future.getResult(); + + authenticationToken = accountManagerResult.getString(AccountManager.KEY_AUTHTOKEN); + + setUpAuthenticationToken(); + + } catch (OperationCanceledException e) { + tearDownActivity(); + } catch (IOException | AuthenticatorException e) { + throw new RuntimeException(e); + } + } + + private void setUpServersContent(String authenticationToken) { + amahiClient.getServers(getActivity(), authenticationToken); + } + + @Subscribe + public void onServersLoaded(ServersLoadedEvent event) { + setUpServersContent(event.getServers()); + } + + private void setUpServersContent(List servers) { + ArrayList activeServers = filterActiveServers(servers); + String[] serverNames = new String[activeServers.size()]; + String[] serverSessions = new String[activeServers.size()]; + + for (int i = 0; i < activeServers.size(); i++) { + Server activeServer = activeServers.get(i); + serverNames[i] = activeServer.getName(); + serverSessions[i] = activeServer.getSession(); + } + + getHdaPreference().setEntries(serverNames); + getHdaPreference().setEntryValues(serverSessions); + getHdaPreference().setEnabled(true); + + String session = getHdaPreference().getValue(); + if (session != null) { + setUpServer(session); + } + } + + private ArrayList filterActiveServers(List servers) { + ArrayList activeServers = new ArrayList<>(); + + for (Server server : servers) { + if (server.isActive()) { + activeServers.add(server); + } + } + + return activeServers; + } + + private boolean isUploadEnabled() { + PreferenceManager preferenceManager = getPreferenceManager(); + return preferenceManager.getSharedPreferences() + .getBoolean(getString(R.string.preference_key_upload_switch), false); + } + + private String getAutoUploadTitle(boolean isUploadEnabled) { + return isUploadEnabled ? "Disable" : "Enable"; + } + + private void setUpSettingsListeners() { + getAutoUploadSwitchPreference().setOnPreferenceChangeListener(this); + getHdaPreference().setOnPreferenceChangeListener(this); + getSharePreference().setOnPreferenceChangeListener(this); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + String key = preference.getKey(); + if (key.equals(getString(R.string.preference_key_upload_switch))) { + boolean isUploadEnabled = (boolean) newValue; + if (isUploadEnabled) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + checkReadPermissions(); + return false; + } + } + toggleUploadSettings(isUploadEnabled); + preference.setTitle(getAutoUploadTitle(isUploadEnabled)); + } else if (key.equals(getString(R.string.preference_key_upload_hda))) { + setUpServer(String.valueOf(newValue)); + } else if (key.equals(getString(R.string.preference_key_upload_share))) { + getPathPreference().setEnabled(true); + getAllowOnDataPreference().setEnabled(true); + } + return true; + } + + private void setUpServer(String session) { + getSharePreference().setEnabled(false); + getPathPreference().setEnabled(false); + getAllowOnDataPreference().setEnabled(false); + + Server server = new Server(session); + setUpServerConnection(server); + } + + @Subscribe + public void onServerConnected(ServerConnectedEvent event) { + setUpServerConnection(); + } + + private void setUpServerConnection(Server server) { + if (serverClient.isConnected(server)) { + setUpServerConnection(); + } else { + serverClient.connect(getActivity(), server); + } + } + + private void setUpServerConnection() { + if (!isConnectionAvailable() || isConnectionAuto()) { + serverClient.connectAuto(); + return; + } + + if (isConnectionLocal()) { + serverClient.connectLocal(); + } else { + serverClient.connectRemote(); + } + } + + + @Subscribe + public void onServerConnectionChanged(ServerConnectionChangedEvent event) { + setUpSharesContent(); + } + + private void setUpSharesContent() { + if (serverClient.isConnected()) { + serverClient.getShares(); + } + } + + @Subscribe + public void onSharesLoaded(ServerSharesLoadedEvent event) { + setUpSharesContent(event.getServerShares()); + } + + private void setUpSharesContent(List shares) { + String[] shareNames = new String[shares.size()]; + for (int i = 0; i < shares.size(); i++) { + shareNames[i] = shares.get(i).getName(); + } + getSharePreference().setEntries(shareNames); + getSharePreference().setEntryValues(shareNames); + getSharePreference().setEnabled(true); + + String selectedShare = getSharePreference().getValue(); + if (selectedShare != null) { + getPathPreference().setEnabled(true); + getAllowOnDataPreference().setEnabled(true); + } + } + + private boolean isConnectionAvailable() { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); + + return preferences.contains(getString(R.string.preference_key_server_connection)); + } + + private boolean isConnectionAuto() { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); + String preferenceConnection = preferences.getString(getString(R.string.preference_key_server_connection), null); + + return preferenceConnection.equals(getString(R.string.preference_key_server_connection_auto)); + } + + private boolean isConnectionLocal() { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); + String preferenceConnection = preferences.getString(getString(R.string.preference_key_server_connection), null); + + return preferenceConnection.equals(getString(R.string.preference_key_server_connection_local)); + } + + private void toggleUploadSettings(boolean isUploadEnabled) { + if (isUploadEnabled) { + setUpAuthenticationToken(); + } else { + getHdaPreference().setEnabled(false); + getSharePreference().setEnabled(false); + getPathPreference().setEnabled(false); + getAllowOnDataPreference().setEnabled(false); + } + } + + private Preference getPreference(int id) { + return findPreference(getString(id)); + } + + private SwitchPreference getAutoUploadSwitchPreference() { + return (SwitchPreference) getPreference(R.string.preference_key_upload_switch); + } + + private ListPreference getHdaPreference() { + return (ListPreference) getPreference(R.string.preference_key_upload_hda); + } + + private ListPreference getSharePreference() { + return (ListPreference) getPreference(R.string.preference_key_upload_share); + } + + private EditTextPreference getPathPreference() { + return (EditTextPreference) getPreference(R.string.preference_key_upload_path); + } + + private SwitchPreference getAllowOnDataPreference() { + return (SwitchPreference) getPreference(R.string.preference_key_upload_data); + } + + private void tearDownActivity() { + getActivity().finish(); + } + + + @Override + public void onResume() { + super.onResume(); + + BusProvider.getBus().register(this); + } + + @Override + public void onPause() { + super.onPause(); + + BusProvider.getBus().unregister(this); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + + // Forward results to EasyPermissions + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); + } + + @Override + public void onPermissionsGranted(int requestCode, List perms) { + if (requestCode == READ_PERMISSIONS) { + toggleUploadSettings(true); + getAutoUploadSwitchPreference().setTitle(getAutoUploadTitle(true)); + } + } + + @Override + public void onPermissionsDenied(int requestCode, List perms) { + if (requestCode == READ_PERMISSIONS) { + showPermissionSnackBar(getString(R.string.file_upload_permission_denied)); + } + } + + private void showPermissionSnackBar(String message) { + Snackbar.make(getView(), message, Snackbar.LENGTH_LONG) + .setAction(R.string.menu_settings, new View.OnClickListener() { + @Override + public void onClick(View v) { + new AppSettingsDialog.Builder(UploadSettingsFragment.this).build().show(); + } + }) + .show(); + } + + @RequiresApi(api = Build.VERSION_CODES.M) + private void checkReadPermissions() { + String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE}; + if (!EasyPermissions.hasPermissions(getContext(), perms)) { + EasyPermissions.requestPermissions(this, getString(R.string.file_upload_permission), + READ_PERMISSIONS, perms); + } + } +} diff --git a/src/main/java/org/amahi/anywhere/job/NetConnectivityJob.java b/src/main/java/org/amahi/anywhere/job/NetConnectivityJob.java new file mode 100644 index 000000000..2e5f08a08 --- /dev/null +++ b/src/main/java/org/amahi/anywhere/job/NetConnectivityJob.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2014 Amahi + * + * This file is part of Amahi. + * + * Amahi is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Amahi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Amahi. If not, see . + */ + +package org.amahi.anywhere.job; + +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.provider.MediaStore; +import android.support.annotation.RequiresApi; +import android.util.Log; + +import org.amahi.anywhere.AmahiApplication.JobIds; +import org.amahi.anywhere.service.UploadService; +import org.amahi.anywhere.util.Intents; + +import java.util.ArrayList; +import java.util.List; + +/** + * Job to monitor when there is a change to photos in the media provider. + */ +@RequiresApi(api = Build.VERSION_CODES.N) +public class NetConnectivityJob extends JobService { + private final String TAG = this.getClass().getName(); + + // A pre-built JobInfo we use for scheduling our job. + static final JobInfo JOB_INFO; + + static { + JobInfo.Builder builder = new JobInfo.Builder(JobIds.NET_CONNECTIVITY_JOB, + new ComponentName("org.amahi.anywhere", NetConnectivityJob.class.getName())); + builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); + JOB_INFO = builder.build(); + } + + // Schedule this job, replace any existing one. + public static void scheduleJob(Context context) { + JobScheduler js = context.getSystemService(JobScheduler.class); + js.schedule(JOB_INFO); + Log.i("NetworkConnectivityJob", "JOB SCHEDULED!"); + } + + // Check whether this job is currently scheduled. + public static boolean isScheduled(Context context) { + JobScheduler js = context.getSystemService(JobScheduler.class); + JobInfo job = js.getPendingJob(JobIds.NET_CONNECTIVITY_JOB); + return job != null; + } + + // Cancel this job, if currently scheduled. + public static void cancelJob(Context context) { + JobScheduler js = context.getSystemService(JobScheduler.class); + js.cancel(JobIds.NET_CONNECTIVITY_JOB); + } + + @Override + public boolean onStartJob(JobParameters params) { + Log.i(TAG, "JOB STARTED!"); + Intent intent = new Intent(this, UploadService.class); + startService(intent); + return false; + } + + @Override + public boolean onStopJob(JobParameters params) { + return false; + } +} diff --git a/src/main/java/org/amahi/anywhere/job/PhotosContentJob.java b/src/main/java/org/amahi/anywhere/job/PhotosContentJob.java new file mode 100644 index 000000000..b734e717d --- /dev/null +++ b/src/main/java/org/amahi/anywhere/job/PhotosContentJob.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2014 Amahi + * + * This file is part of Amahi. + * + * Amahi is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Amahi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Amahi. If not, see . + */ + +package org.amahi.anywhere.job; + +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.provider.MediaStore; +import android.support.annotation.RequiresApi; +import android.util.Log; + +import org.amahi.anywhere.AmahiApplication.JobIds; +import org.amahi.anywhere.util.Intents; + +import java.util.ArrayList; +import java.util.List; + +/** + * Job to monitor when there is a change to photos in the media provider. + */ +@RequiresApi(api = Build.VERSION_CODES.N) +public class PhotosContentJob extends JobService { + private final String TAG = this.getClass().getName(); + + // The root URI of the media provider, to monitor for generic changes to its content. + static final Uri MEDIA_URI = Uri.parse("content://" + MediaStore.AUTHORITY + "/"); + + // Path segments for image-specific URIs in the provider. + static final List EXTERNAL_PATH_SEGMENTS + = MediaStore.Images.Media.EXTERNAL_CONTENT_URI.getPathSegments(); + + // A pre-built JobInfo we use for scheduling our job. + static final JobInfo JOB_INFO; + + static { + JobInfo.Builder builder = new JobInfo.Builder(JobIds.PHOTOS_CONTENT_JOB, + new ComponentName("org.amahi.anywhere", PhotosContentJob.class.getName())); + // Look for specific changes to images in the provider. + builder.addTriggerContentUri(new JobInfo.TriggerContentUri( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS)); + // Also look for general reports of changes in the overall provider. + builder.addTriggerContentUri(new JobInfo.TriggerContentUri(MEDIA_URI, 0)); + JOB_INFO = builder.build(); + } + + JobParameters mRunningParams; + + // Schedule this job, replace any existing one. + public static void scheduleJob(Context context) { + JobScheduler js = context.getSystemService(JobScheduler.class); + js.schedule(JOB_INFO); + Log.i("PhotosContentJob", "JOB SCHEDULED!"); + } + + // Check whether this job is currently scheduled. + public static boolean isScheduled(Context context) { + JobScheduler js = context.getSystemService(JobScheduler.class); + JobInfo job = js.getPendingJob(JobIds.PHOTOS_CONTENT_JOB); + return job != null; + } + + // Cancel this job, if currently scheduled. + public static void cancelJob(Context context) { + JobScheduler js = context.getSystemService(JobScheduler.class); + js.cancel(JobIds.PHOTOS_CONTENT_JOB); + } + + @Override + public boolean onStartJob(JobParameters params) { + Log.i("PhotosContentJob", "JOB STARTED!"); + mRunningParams = params; + + // Did we trigger due to a content change? + if (params.getTriggeredContentAuthorities() != null) { + if (params.getTriggeredContentUris() != null) { + // If we have details about which URIs changed, then iterate through them + // and collect valid uris and send them to UploadService + ArrayList uris = new ArrayList<>(); + for (Uri uri : params.getTriggeredContentUris()) { + List path = uri.getPathSegments(); + if (path != null && path.size() == EXTERNAL_PATH_SEGMENTS.size() + 1) { + // This is a specific file. + uris.add(uri); + } + } + Intent intent = Intents.Builder.with(this).buildUploadServiceIntent(uris); + startService(intent); + } else { + // We don't have any details about URIs (because too many changed at once) + Log.i(TAG, "Photos rescan needed!"); + } + } else { + Log.i(TAG, "(No photos content)"); + } + + scheduleJob(PhotosContentJob.this); + return false; + } + + @Override + public boolean onStopJob(JobParameters params) { + return false; + } +} diff --git a/src/main/java/org/amahi/anywhere/model/UploadFile.java b/src/main/java/org/amahi/anywhere/model/UploadFile.java new file mode 100644 index 000000000..6b24344f3 --- /dev/null +++ b/src/main/java/org/amahi/anywhere/model/UploadFile.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2014 Amahi + * + * This file is part of Amahi. + * + * Amahi is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Amahi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Amahi. If not, see . + */ + +package org.amahi.anywhere.model; + +public class UploadFile { + private int id; + private String path; + + public UploadFile(int id, String path) { + this.id = id; + this.path = path; + } + + public int getId() { + return id; + } + + public String getPath() { + return path; + } +} diff --git a/src/main/java/org/amahi/anywhere/receiver/CameraReceiver.java b/src/main/java/org/amahi/anywhere/receiver/CameraReceiver.java new file mode 100644 index 000000000..f0a8a7e23 --- /dev/null +++ b/src/main/java/org/amahi/anywhere/receiver/CameraReceiver.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2014 Amahi + * + * This file is part of Amahi. + * + * Amahi is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Amahi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Amahi. If not, see . + */ + +package org.amahi.anywhere.receiver; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.util.Log; + +import org.amahi.anywhere.service.UploadService; +import org.amahi.anywhere.util.Intents; + +import java.util.ArrayList; + +/** + * Camera new picture event receiver. + */ +public class CameraReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + Log.e("NEW_IMAGE", "onReceive"); + Intent uploadService = Intents.Builder.with(context).buildUploadServiceIntent(intent.getData()); + context.startService(uploadService); + } +} diff --git a/src/main/java/org/amahi/anywhere/receiver/NetworkReceiver.java b/src/main/java/org/amahi/anywhere/receiver/NetworkReceiver.java index 5c4d7489b..a1054082d 100644 --- a/src/main/java/org/amahi/anywhere/receiver/NetworkReceiver.java +++ b/src/main/java/org/amahi/anywhere/receiver/NetworkReceiver.java @@ -27,13 +27,14 @@ import org.amahi.anywhere.bus.BusProvider; import org.amahi.anywhere.bus.NetworkChangedEvent; +import org.amahi.anywhere.service.UploadService; +import org.amahi.anywhere.util.NetworkUtils; /** * Network system events receiver. Proxies system network events such as changing network connection * to the local {@link com.squareup.otto.Bus} as {@link org.amahi.anywhere.bus.BusEvent}. */ -public class NetworkReceiver extends BroadcastReceiver -{ +public class NetworkReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) { @@ -42,22 +43,28 @@ public void onReceive(Context context, Intent intent) { } private void handleNetworkChangeEvent(Context context) { - NetworkInfo network = getNetwork(context); - - if (isNetworkConnected(network)) { + NetworkUtils networkUtils = new NetworkUtils(context); + NetworkInfo network = networkUtils.getNetwork(); + if (networkUtils.isNetworkConnected(network)) { BusProvider.getBus().post(new NetworkChangedEvent(network.getType())); } - } - private NetworkInfo getNetwork(Context context) { - return getNetworkManager(context).getActiveNetworkInfo(); + if (networkUtils.isUploadAllowed()) { + startUploadService(context); + } else { + stopUploadService(context); + } } - private ConnectivityManager getNetworkManager(Context context) { - return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + private void startUploadService(Context context) { + Intent uploadService = new Intent(context, UploadService.class); + context.startService(uploadService); } - private boolean isNetworkConnected(NetworkInfo network) { - return (network != null) && network.isConnected(); + private void stopUploadService(Context context) { + Intent uploadService = new Intent(context, UploadService.class); + context.stopService(uploadService); } + + } diff --git a/src/main/java/org/amahi/anywhere/server/client/ServerClient.java b/src/main/java/org/amahi/anywhere/server/client/ServerClient.java index b6c3351d0..afe1c6853 100644 --- a/src/main/java/org/amahi/anywhere/server/client/ServerClient.java +++ b/src/main/java/org/amahi/anywhere/server/client/ServerClient.java @@ -29,6 +29,7 @@ import org.amahi.anywhere.bus.ServerConnectedEvent; import org.amahi.anywhere.bus.ServerConnectionChangedEvent; import org.amahi.anywhere.bus.ServerConnectionDetectedEvent; +import org.amahi.anywhere.bus.ServerFileUploadCompleteEvent; import org.amahi.anywhere.bus.ServerRouteLoadedEvent; import org.amahi.anywhere.server.Api; import org.amahi.anywhere.server.ApiAdapter; @@ -54,12 +55,15 @@ import org.json.JSONObject; import java.io.File; +import java.io.IOException; import javax.inject.Inject; import javax.inject.Singleton; import okhttp3.MultipartBody; +import okhttp3.ResponseBody; import retrofit2.Callback; +import retrofit2.Response; import static org.amahi.anywhere.util.Android.loadServersFromAsset; @@ -242,20 +246,45 @@ public void deleteFile(ServerShare share, ServerFile serverFile) { .enqueue(new ServerFileDeleteResponse()); } - public void uploadFile(File file, ServerShare share) { - this.uploadFile(file, share, null); + private MultipartBody.Part createFilePart(int id, File file) { + return MultipartBody.Part.createFormData("file", + file.getName(), + new ProgressRequestBody(id, file)); } - public void uploadFile(File file, ServerShare share, ServerFile directory) { - MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", - file.getName(), - new ProgressRequestBody(file)); + public void uploadFile(int id, File file, String shareName, String path) { + MultipartBody.Part filePart = createFilePart(id, file); + uploadFileAsync(id, filePart, shareName, path); + } + + public void uploadFile(int id, File file, ServerShare share, ServerFile directory) { + MultipartBody.Part filePart = createFilePart(id, file); String path = "/"; if (directory != null) path = directory.getPath(); + uploadFileAsync(id, filePart, share.getName(), path); + } - serverApi.uploadFile(server.getSession(), share.getName(), path, filePart) - .enqueue(new ServerFileUploadResponse()); + private void uploadFileAsync(int id, MultipartBody.Part filePart, String shareName, String path) { + serverApi.uploadFile(server.getSession(), shareName, path, filePart) + .enqueue(new ServerFileUploadResponse(id)); + } + + private void uploadFileSync(int id, MultipartBody.Part filePart, String shareName, String path) { + try { + Response response = serverApi + .uploadFile(server.getSession(), shareName, path, filePart) + .execute(); + if (response.isSuccessful()) { + BusProvider.getBus().post( + new ServerFileUploadCompleteEvent(id, true)); + } else { + BusProvider.getBus().post( + new ServerFileUploadCompleteEvent(id, false)); + } + } catch (IOException e) { + e.printStackTrace(); + } } public Uri getFileUri(ServerShare share, ServerFile file) { diff --git a/src/main/java/org/amahi/anywhere/server/model/Server.java b/src/main/java/org/amahi/anywhere/server/model/Server.java index 42df9fe0c..5cb6d81ea 100644 --- a/src/main/java/org/amahi/anywhere/server/model/Server.java +++ b/src/main/java/org/amahi/anywhere/server/model/Server.java @@ -82,6 +82,10 @@ public Server(int index, String name, String session) { this.debug = true; } + public Server(String session) { + this.session = session; + } + public Server(Parcel parcel) { this.name = parcel.readString(); this.session = parcel.readString(); diff --git a/src/main/java/org/amahi/anywhere/server/response/ServerFileUploadResponse.java b/src/main/java/org/amahi/anywhere/server/response/ServerFileUploadResponse.java index e58e5ad7e..25fc0354d 100644 --- a/src/main/java/org/amahi/anywhere/server/response/ServerFileUploadResponse.java +++ b/src/main/java/org/amahi/anywhere/server/response/ServerFileUploadResponse.java @@ -34,16 +34,22 @@ * as {@link org.amahi.anywhere.bus.BusEvent}. */ public class ServerFileUploadResponse implements Callback { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - BusProvider.getBus().post(new ServerFileUploadCompleteEvent(true)); - } else - this.onFailure(call, new HttpException(response)); - } - - @Override - public void onFailure(Call call, Throwable t) { - BusProvider.getBus().post(new ServerFileUploadCompleteEvent(false)); - } + private int id; + + public ServerFileUploadResponse(int id) { + this.id = id; + } + + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + BusProvider.getBus().post(new ServerFileUploadCompleteEvent(id, true)); + } else + this.onFailure(call, new HttpException(response)); + } + + @Override + public void onFailure(Call call, Throwable t) { + BusProvider.getBus().post(new ServerFileUploadCompleteEvent(id, false)); + } } diff --git a/src/main/java/org/amahi/anywhere/service/UploadService.java b/src/main/java/org/amahi/anywhere/service/UploadService.java new file mode 100644 index 000000000..ec6ba5e5a --- /dev/null +++ b/src/main/java/org/amahi/anywhere/service/UploadService.java @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2014 Amahi + * + * This file is part of Amahi. + * + * Amahi is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Amahi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Amahi. If not, see . + */ + +package org.amahi.anywhere.service; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.IBinder; +import android.preference.PreferenceManager; +import android.provider.MediaStore; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.NotificationCompat; + +import com.squareup.otto.Subscribe; + +import org.amahi.anywhere.AmahiApplication; +import org.amahi.anywhere.R; +import org.amahi.anywhere.bus.BusProvider; +import org.amahi.anywhere.bus.ServerConnectedEvent; +import org.amahi.anywhere.bus.ServerConnectionChangedEvent; +import org.amahi.anywhere.db.UploadQueueDbHelper; +import org.amahi.anywhere.job.NetConnectivityJob; +import org.amahi.anywhere.model.UploadFile; +import org.amahi.anywhere.server.client.ServerClient; +import org.amahi.anywhere.server.model.Server; +import org.amahi.anywhere.util.Intents; +import org.amahi.anywhere.util.NetworkUtils; +import org.amahi.anywhere.util.UploadManager; + +import java.util.ArrayList; + +import javax.inject.Inject; + +/** + * File upload service + */ +public class UploadService extends Service implements UploadManager.UploadCallbacks { + + @Inject + ServerClient serverClient; + + private UploadManager uploadManager; + private UploadQueueDbHelper uploadQueueDbHelper; + private NotificationCompat.Builder notificationBuilder; + private NetworkUtils networkUtils; + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + setUpInjections(); + setUpBus(); + setUpDbHelper(); + setUpNetworkUtils(); + } + + private void setUpInjections() { + AmahiApplication.from(this).inject(this); + } + + private void setUpBus() { + BusProvider.getBus().register(this); + } + + private void setUpDbHelper() { + uploadQueueDbHelper = UploadQueueDbHelper.init(this); + } + + private void setUpNetworkUtils() { + networkUtils = new NetworkUtils(this); + } + + @Override + public int onStartCommand(@Nullable Intent intent, int flags, int startId) { + + if (intent != null && intent.hasExtra(Intents.Extras.IMAGE_URIS)) { + if (isAutoUploadEnabled()) { + ArrayList uris = intent.getParcelableArrayListExtra(Intents.Extras.IMAGE_URIS); + for (Uri uri : uris) { + String imagePath = queryImagePath(uri); + if (imagePath != null) { + UploadFile uploadFile = uploadQueueDbHelper.addNewImagePath(imagePath); + if (uploadFile != null && uploadManager != null) + uploadManager.add(uploadFile); + } + } + } + } + + if (isAutoUploadEnabled()) { + if (isUploadAllowed()) { + connectToServer(); + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + NetConnectivityJob.scheduleJob(this); + } + } + } + + return super.onStartCommand(intent, flags, startId); + } + + private boolean isAutoUploadEnabled() { + return PreferenceManager.getDefaultSharedPreferences(this) + .getBoolean(getString(R.string.preference_key_upload_switch), false); + } + + private boolean isUploadAllowed() { + return networkUtils.isUploadAllowed(); + } + + private void connectToServer() { + Server server = getUploadServer(); + if (server != null) { + setUpServerConnection(server); + } + } + + private void setUpServerConnection(@NonNull Server server) { + if (serverClient.isConnected(server)) { + setUpServerConnection(); + } else { + serverClient.connect(this, server); + } + } + + @Subscribe + public void onServerConnected(ServerConnectedEvent event) { + Server uploadServer = getUploadServer(); + if (uploadServer != null && uploadServer == event.getServer()) { + setUpServerConnection(); + } + } + + private void setUpServerConnection() { + if (!isConnectionAvailable() || isConnectionAuto()) { + serverClient.connectAuto(); + return; + } + + if (isConnectionLocal()) { + serverClient.connectLocal(); + } else { + serverClient.connectRemote(); + } + } + + private boolean isConnectionAvailable() { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + + return preferences.contains(getString(R.string.preference_key_server_connection)); + } + + private boolean isConnectionAuto() { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + String preferenceConnection = preferences.getString(getString(R.string.preference_key_server_connection), null); + + return preferenceConnection.equals(getString(R.string.preference_key_server_connection_auto)); + } + + private boolean isConnectionLocal() { + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + String preferenceConnection = preferences.getString(getString(R.string.preference_key_server_connection), null); + + return preferenceConnection.equals(getString(R.string.preference_key_server_connection_local)); + } + + @Subscribe + public void onServerConnectionChanged(ServerConnectionChangedEvent event) { + if (uploadManager == null) { + setUpUploadManager(); + } + uploadManager.startUploading(); + } + + private Server getUploadServer() { + String session = PreferenceManager.getDefaultSharedPreferences(this) + .getString(getString(R.string.preference_key_upload_hda), null); + if (session != null) { + return new Server(session); + } else { + return null; + } + } + + private void setUpUploadManager() { + ArrayList uploadFiles = uploadQueueDbHelper.getAllImagePaths(); + uploadManager = new UploadManager(this, uploadFiles); + } + + private String queryImagePath(Uri imageUri) { + String filePath = null; + if ("content".equals(imageUri.getScheme())) { + Cursor cursor = this.getContentResolver() + .query(imageUri, null, null, null, null); + if (cursor != null) { + cursor.moveToFirst(); + int columnIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA); + filePath = cursor.getString(columnIndex); + cursor.close(); + } + } else { + filePath = imageUri.toString(); + } + return filePath; + } + + @Override + public void uploadStarted(int id, String fileName) { + notificationBuilder = new NotificationCompat.Builder(getApplicationContext()); + notificationBuilder + .setOngoing(true) + .setSmallIcon(R.drawable.ic_app_logo) + .setContentTitle(getString(R.string.notification_upload_title)) + .setContentText(getString(R.string.notification_upload_message, fileName)) + .setProgress(100, 0, false) + .build(); + Notification notification = notificationBuilder.build(); + startForeground(id, notification); + } + + @Override + public void uploadProgress(int id, int progress) { + NotificationManager notificationManager = (NotificationManager) getApplicationContext() + .getSystemService(Context.NOTIFICATION_SERVICE); + notificationBuilder + .setProgress(100, progress, false); + Notification notification = notificationBuilder.build(); + notificationManager.notify(id, notification); + } + + @Override + public void uploadSuccess(int id) { + uploadComplete(id, getString(R.string.message_upload_success)); + } + + @Override + public void uploadError(int id) { + uploadComplete(id, getString(R.string.message_upload_error)); + } + + private void uploadComplete(int id, String title) { + stopForeground(false); + NotificationManager notificationManager = (NotificationManager) getApplicationContext() + .getSystemService(Context.NOTIFICATION_SERVICE); + + notificationBuilder + .setContentTitle(title) + .setOngoing(false) + .setProgress(0, 0, false); + + Notification notification = notificationBuilder.build(); + notificationManager.notify(id, notification); + } + + @Override + public void removeFileFromDb(int id) { + uploadQueueDbHelper.removeImagePath(id); + } + + @Override + public void uploadQueueFinished() { + tearDownUploadManager(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + uploadQueueDbHelper.closeDataBase(); + tearDownUploadManager(); + tearDownBus(); + } + + private void tearDownUploadManager() { + if (uploadManager != null) { + uploadManager.tearDownBus(); + uploadManager = null; + } + } + + public void tearDownBus() { + BusProvider.getBus().unregister(this); + } +} diff --git a/src/main/java/org/amahi/anywhere/util/Intents.java b/src/main/java/org/amahi/anywhere/util/Intents.java index a0be37013..e0cc8ca6a 100644 --- a/src/main/java/org/amahi/anywhere/util/Intents.java +++ b/src/main/java/org/amahi/anywhere/util/Intents.java @@ -42,6 +42,7 @@ import org.amahi.anywhere.server.model.ServerApp; import org.amahi.anywhere.server.model.ServerFile; import org.amahi.anywhere.server.model.ServerShare; +import org.amahi.anywhere.service.UploadService; import org.amahi.anywhere.tv.activity.ServerFileTvActivity; import org.amahi.anywhere.tv.activity.TVWebViewActivity; import org.amahi.anywhere.tv.activity.TvPlaybackOverlayActivity; @@ -61,6 +62,7 @@ public static final class Extras { public static final String SERVER_FILE = "server_file"; public static final String SERVER_FILES = "server_files"; public static final String SERVER_SHARE = "server_share"; + public static final String IMAGE_URIS = "image_uris"; private Extras() { } } @@ -228,5 +230,17 @@ public Intent buildMediaPickerIntent () { public Intent buildCameraIntent() { return new Intent(MediaStore.ACTION_IMAGE_CAPTURE); } + + public Intent buildUploadServiceIntent(Uri uri) { + ArrayList uris = new ArrayList<>(); + uris.add(uri); + return buildUploadServiceIntent(uris); + } + + public Intent buildUploadServiceIntent(ArrayList uris) { + Intent uploadService = new Intent(context, UploadService.class); + uploadService.putParcelableArrayListExtra(Extras.IMAGE_URIS, uris); + return uploadService; + } } } diff --git a/src/main/java/org/amahi/anywhere/util/NetworkUtils.java b/src/main/java/org/amahi/anywhere/util/NetworkUtils.java new file mode 100644 index 000000000..d430fc88c --- /dev/null +++ b/src/main/java/org/amahi/anywhere/util/NetworkUtils.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2014 Amahi + * + * This file is part of Amahi. + * + * Amahi is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Amahi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Amahi. If not, see . + */ + +package org.amahi.anywhere.util; + + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.preference.PreferenceManager; + +import org.amahi.anywhere.R; + +/** + * Network utility methods to check the current connected network + */ +public class NetworkUtils { + + private Context context; + + public NetworkUtils(Context context) { + this.context = context; + } + + public NetworkInfo getNetwork() { + return getNetworkManager().getActiveNetworkInfo(); + } + + private ConnectivityManager getNetworkManager() { + return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + } + + public boolean isNetworkConnected(NetworkInfo network) { + return (network != null) && network.isConnected(); + } + + public boolean isUploadAllowed() { + NetworkInfo network = getNetwork(); + return isNetworkConnected(network) && + (network.getType() != ConnectivityManager.TYPE_MOBILE || + isUploadAllowedOnMobileData()); + } + + private boolean isUploadAllowedOnMobileData() { + return PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(context.getString(R.string.preference_key_upload_data), false); + } +} diff --git a/src/main/java/org/amahi/anywhere/util/ProgressRequestBody.java b/src/main/java/org/amahi/anywhere/util/ProgressRequestBody.java index 24eb0f385..b75052ddf 100644 --- a/src/main/java/org/amahi/anywhere/util/ProgressRequestBody.java +++ b/src/main/java/org/amahi/anywhere/util/ProgressRequestBody.java @@ -38,61 +38,68 @@ * for file upload. */ public class ProgressRequestBody extends RequestBody { - private File mFile; - - private static final int DEFAULT_BUFFER_SIZE = 2048; - - public ProgressRequestBody(final File file) { - mFile = file; - } - - @Override - public MediaType contentType() { - // Only for uploading images - return MediaType.parse("image/*"); - } - - @Override - public long contentLength() throws IOException { - return mFile.length(); - } - - @Override - public void writeTo(BufferedSink sink) throws IOException { - long fileLength = mFile.length(); - byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; - FileInputStream in = new FileInputStream(mFile); - long uploaded = 0; - - //noinspection TryFinallyCanBeTryWithResources - try { - int read; - Handler handler = new Handler(Looper.getMainLooper()); - while ((read = in.read(buffer)) != -1) { - - uploaded += read; - sink.write(buffer, 0, read); - - // update progress on UI thread - handler.post(new ProgressUpdater(uploaded, fileLength)); - } - } finally { - in.close(); - } - } - - private class ProgressUpdater implements Runnable { - private long mUploaded; - private long mTotal; - - ProgressUpdater(long uploaded, long total) { - mUploaded = uploaded; - mTotal = total; - } - - @Override - public void run() { - BusProvider.getBus().post(new ServerFileUploadProgressEvent((int) (100 * mUploaded / mTotal))); - } - } + private int mId; + private File mFile; + private int lastProgress = 0; + + private static final int DEFAULT_BUFFER_SIZE = 2048; + + public ProgressRequestBody(int id, File file) { + mId = id; + mFile = new File(file.getPath()); + } + + @Override + public MediaType contentType() { + // Only for uploading images + return MediaType.parse("image/*"); + } + + @Override + public long contentLength() throws IOException { + return mFile.length(); + } + + @Override + public void writeTo(BufferedSink sink) throws IOException { + long fileLength = mFile.length(); + byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; + FileInputStream in = new FileInputStream(mFile); + long uploaded = 0; + + //noinspection TryFinallyCanBeTryWithResources + try { + int read; + Handler handler = new Handler(Looper.getMainLooper()); + while ((read = in.read(buffer)) != -1) { + + uploaded += read; + sink.write(buffer, 0, read); + + // update progress on UI thread + handler.post(new ProgressUpdater(uploaded, fileLength)); + } + } finally { + in.close(); + } + } + + private class ProgressUpdater implements Runnable { + private long mUploaded; + private long mTotal; + + ProgressUpdater(long uploaded, long total) { + mUploaded = uploaded; + mTotal = total; + } + + @Override + public void run() { + int progress = (int) (100 * mUploaded / mTotal); + if (lastProgress != progress) { + lastProgress = progress; + BusProvider.getBus().post(new ServerFileUploadProgressEvent(mId, progress)); + } + } + } } diff --git a/src/main/java/org/amahi/anywhere/util/UploadManager.java b/src/main/java/org/amahi/anywhere/util/UploadManager.java new file mode 100644 index 000000000..647a83e58 --- /dev/null +++ b/src/main/java/org/amahi/anywhere/util/UploadManager.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2014 Amahi + * + * This file is part of Amahi. + * + * Amahi is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Amahi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Amahi. If not, see . + */ + +package org.amahi.anywhere.util; + +import android.content.Context; +import android.os.Handler; +import android.preference.PreferenceManager; + +import com.squareup.otto.Subscribe; + +import org.amahi.anywhere.AmahiApplication; +import org.amahi.anywhere.R; +import org.amahi.anywhere.bus.BusProvider; +import org.amahi.anywhere.bus.ServerFileUploadCompleteEvent; +import org.amahi.anywhere.bus.ServerFileUploadProgressEvent; +import org.amahi.anywhere.model.UploadFile; +import org.amahi.anywhere.server.client.ServerClient; + +import java.io.File; +import java.util.ArrayList; + +import javax.inject.Inject; + +/** + * An Upload Manager that manages all the uploads one by one present in the queue. + */ +public class UploadManager { + private boolean isRunning = false; + + @Inject + public ServerClient serverClient; + + private Context context; + private ArrayList uploadFiles; + private UploadCallbacks uploadCallbacks; + + public + UploadManager(T context, ArrayList uploadFiles) { + + this.context = context; + this.uploadCallbacks = context; + this.uploadFiles = uploadFiles; + + setUpInjections(); + setUpBus(); + } + + private void setUpInjections() { + AmahiApplication.from(context).inject(this); + } + + private void setUpBus() { + BusProvider.getBus().register(this); + } + + public void tearDownBus() { + BusProvider.getBus().unregister(this); + } + + public void startUploading() { + if (!isRunning) { + isRunning = true; + processNextFile(); + } + } + + private void processNextFile() { + if (uploadFiles.isEmpty()) { + isRunning = false; + uploadCallbacks.uploadQueueFinished(); + } else { + UploadFile currentFile = uploadFiles.remove(0); + upload(currentFile); + } + } + + private void upload(UploadFile uploadFile) { + File image = new File(uploadFile.getPath()); + if (image.exists()) { + uploadCallbacks.uploadStarted(uploadFile.getId(), image.getName()); + serverClient.uploadFile(uploadFile.getId(), image, getUploadShareName(), + getUploadPath()); + } else { + uploadCallbacks.removeFileFromDb(uploadFile.getId()); + processNextFile(); + } + + } + + private String getUploadShareName() { + return PreferenceManager.getDefaultSharedPreferences(context) + .getString(context.getString(R.string.preference_key_upload_share), null); + } + + private String getUploadPath() { + return PreferenceManager.getDefaultSharedPreferences(context) + .getString(context.getString(R.string.preference_key_upload_path), null); + } + + + public void add(UploadFile uploadFile) { + uploadFiles.add(uploadFile); + } + + @Subscribe + public void onFileUploadProgressEvent(ServerFileUploadProgressEvent event) { + uploadCallbacks.uploadProgress(event.getId(), event.getProgress()); + } + + @Subscribe + public void onFileUploadCompleteEvent(ServerFileUploadCompleteEvent event) { + if (event.wasUploadSuccessful()) { + uploadCallbacks.removeFileFromDb(event.getId()); + uploadCallbacks.uploadSuccess(event.getId()); + } else { + uploadCallbacks.uploadError(event.getId()); + } + + final Handler handler = new Handler(); + handler.postDelayed(new Runnable() { + @Override + public void run() { + processNextFile(); + } + }, 500); + } + + public interface UploadCallbacks { + void uploadStarted(int id, String fileName); + + void uploadProgress(int id, int progress); + + void uploadSuccess(int id); + + void uploadError(int id); + + void removeFileFromDb(int id); + + void uploadQueueFinished(); + } +} diff --git a/src/main/res/values/preferences.xml b/src/main/res/values/preferences.xml index 3ad31b746..0792bc9a6 100644 --- a/src/main/res/values/preferences.xml +++ b/src/main/res/values/preferences.xml @@ -22,6 +22,7 @@ About Account Settings + Auto Upload Feedback Rate @@ -29,6 +30,13 @@ Sign out Connection Tell a friend + Auto Upload + + HDA Server + Share + Path + Allow on Cellular Data + Autodetect Remote @@ -43,6 +51,13 @@ remote local tell_a_friend + auto_upload_screen + + auto_upload_switch + auto_upload_hda + auto_upload_share + auto_upload_path + auto_upload_data @string/preference_entry_server_connection_auto diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 536f6810c..d52f0e37d 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -77,6 +77,10 @@ There was some error in uploading your file Overwrite existing file? The file %1$s already exists.\nAre you sure you want to replace the existing file? + Uploading new image + File %1$s + Upload Complete + Upload failed Check out my Amahi home server! I use the Amahi Home Server for storing, backing up and streaming all my files.\n\nCheck it out!\n\nhttps://www.amahi.org/ diff --git a/src/main/res/xml/settings.xml b/src/main/res/xml/settings.xml index e7197a550..23ae988ee 100644 --- a/src/main/res/xml/settings.xml +++ b/src/main/res/xml/settings.xml @@ -23,7 +23,7 @@ + android:title="@string/preference_title_account_sign_out"/> @@ -34,7 +34,11 @@ android:entries="@array/preference_entries_server_connection" android:entryValues="@array/preference_entries_keys_server_connection" android:key="@string/preference_key_server_connection" - android:title="@string/preference_title_server_connection" /> + android:title="@string/preference_title_server_connection"/> + + @@ -42,19 +46,19 @@ + android:title="@string/preference_title_about_version"/> + android:title="@string/preference_title_about_feedback"/> + android:title="@string/preference_title_about_rating"/> + android:title="@string/preference_title_tell_a_friend"/> diff --git a/src/main/res/xml/upload_settings.xml b/src/main/res/xml/upload_settings.xml new file mode 100644 index 000000000..052e6b7a7 --- /dev/null +++ b/src/main/res/xml/upload_settings.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + +