395 lines
14 KiB
Java
Executable File
395 lines
14 KiB
Java
Executable File
/*
|
|
* Nextcloud Android client application
|
|
*
|
|
* @author Tobias Kaminsky
|
|
* @author Chris Narkiewicz
|
|
* Copyright (C) 2018 Tobias Kaminsky
|
|
* Copyright (C) 2018 Nextcloud
|
|
* Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
|
* License as published by the Free Software Foundation; either
|
|
* version 3 of the License, or any later version.
|
|
*
|
|
* This program 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 AFFERO GENERAL PUBLIC LICENSE for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public
|
|
* License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package com.owncloud.android.ui.activity;
|
|
|
|
import android.accounts.Account;
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.IntentFilter;
|
|
import android.content.ServiceConnection;
|
|
import android.os.Bundle;
|
|
import android.os.IBinder;
|
|
import android.view.Menu;
|
|
import android.view.MenuInflater;
|
|
import android.view.MenuItem;
|
|
import android.view.View;
|
|
import android.widget.ImageView;
|
|
import android.widget.TextView;
|
|
|
|
import com.evernote.android.job.Job;
|
|
import com.evernote.android.job.JobManager;
|
|
import com.evernote.android.job.JobRequest;
|
|
import com.evernote.android.job.util.support.PersistableBundleCompat;
|
|
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
|
import com.nextcloud.client.account.UserAccountManager;
|
|
import com.owncloud.android.R;
|
|
import com.owncloud.android.datamodel.UploadsStorageManager;
|
|
import com.owncloud.android.files.services.FileUploader;
|
|
import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
|
|
import com.owncloud.android.jobs.FilesSyncJob;
|
|
import com.owncloud.android.lib.common.operations.RemoteOperation;
|
|
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
|
|
import com.owncloud.android.lib.common.utils.Log_OC;
|
|
import com.owncloud.android.operations.CheckCurrentCredentialsOperation;
|
|
import com.owncloud.android.ui.EmptyRecyclerView;
|
|
import com.owncloud.android.ui.adapter.UploadListAdapter;
|
|
import com.owncloud.android.ui.decoration.MediaGridItemDecoration;
|
|
import com.owncloud.android.utils.DisplayUtils;
|
|
import com.owncloud.android.utils.FilesSyncHelper;
|
|
import com.owncloud.android.utils.ThemeUtils;
|
|
|
|
import java.util.Set;
|
|
|
|
import javax.inject.Inject;
|
|
|
|
import androidx.recyclerview.widget.GridLayoutManager;
|
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
|
import butterknife.BindString;
|
|
import butterknife.BindView;
|
|
import butterknife.ButterKnife;
|
|
import butterknife.Unbinder;
|
|
|
|
/**
|
|
* Activity listing pending, active, and completed uploads. User can delete
|
|
* completed uploads from view. Content of this list of coming from
|
|
* {@link UploadsStorageManager}.
|
|
*/
|
|
public class UploadListActivity extends FileActivity {
|
|
|
|
private static final String TAG = UploadListActivity.class.getSimpleName();
|
|
|
|
private UploadMessagesReceiver uploadMessagesReceiver;
|
|
|
|
private UploadListAdapter uploadListAdapter;
|
|
|
|
public SwipeRefreshLayout swipeListRefreshLayout;
|
|
|
|
@BindView(R.id.empty_list_view_text)
|
|
public TextView emptyContentMessage;
|
|
|
|
@BindView(R.id.empty_list_view_headline)
|
|
public TextView emptyContentHeadline;
|
|
|
|
@BindView(R.id.empty_list_icon)
|
|
public ImageView emptyContentIcon;
|
|
|
|
@BindView(android.R.id.list)
|
|
public EmptyRecyclerView recyclerView;
|
|
|
|
@BindString(R.string.upload_list_empty_headline)
|
|
public String noResultsHeadline;
|
|
|
|
@BindString(R.string.upload_list_empty_text_auto_upload)
|
|
public String noResultsMessage;
|
|
|
|
private Unbinder unbinder;
|
|
|
|
@Inject
|
|
protected UserAccountManager userAccountManager;
|
|
|
|
@Inject
|
|
protected UploadsStorageManager uploadsStorageManager;
|
|
|
|
@Override
|
|
public void showFiles(boolean onDeviceOnly) {
|
|
super.showFiles(onDeviceOnly);
|
|
Intent i = new Intent(getApplicationContext(), FileDisplayActivity.class);
|
|
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
startActivity(i);
|
|
}
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
|
|
setContentView(R.layout.upload_list_layout);
|
|
unbinder = ButterKnife.bind(this);
|
|
|
|
swipeListRefreshLayout = findViewById(R.id.swipe_containing_list);
|
|
|
|
// this activity has no file really bound, it's for multiple accounts at the same time; should no inherit
|
|
// from FileActivity; moreover, some behaviours inherited from FileActivity should be delegated to Fragments;
|
|
// but that's other story
|
|
setFile(null);
|
|
|
|
// setup toolbar
|
|
setupToolbar();
|
|
|
|
// setup drawer
|
|
setupDrawer(R.id.nav_uploads);
|
|
|
|
setupContent();
|
|
|
|
if (getSupportActionBar() != null) {
|
|
ThemeUtils.setColoredTitle(getSupportActionBar(), R.string.uploads_view_title, this);
|
|
}
|
|
|
|
BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_navigation_view);
|
|
|
|
if (getResources().getBoolean(R.bool.bottom_toolbar_enabled)) {
|
|
bottomNavigationView.setVisibility(View.VISIBLE);
|
|
DisplayUtils.setupBottomBar(
|
|
getUserAccountManager().getCurrentAccount(),
|
|
bottomNavigationView,
|
|
getResources(),
|
|
this,
|
|
-1
|
|
);
|
|
}
|
|
}
|
|
|
|
private void setupContent() {
|
|
recyclerView = findViewById(android.R.id.list);
|
|
recyclerView.setEmptyView(findViewById(R.id.empty_list_view));
|
|
findViewById(R.id.empty_list_progress).setVisibility(View.GONE);
|
|
emptyContentIcon.setImageResource(R.drawable.uploads);
|
|
emptyContentIcon.getDrawable().mutate();
|
|
emptyContentIcon.setAlpha(0.5f);
|
|
emptyContentIcon.setVisibility(View.VISIBLE);
|
|
emptyContentHeadline.setText(noResultsHeadline);
|
|
emptyContentMessage.setText(noResultsMessage);
|
|
|
|
uploadListAdapter = new UploadListAdapter(this, uploadsStorageManager);
|
|
|
|
final GridLayoutManager lm = new GridLayoutManager(this, 1);
|
|
uploadListAdapter.setLayoutManager(lm);
|
|
|
|
int spacing = getResources().getDimensionPixelSize(R.dimen.media_grid_spacing);
|
|
recyclerView.addItemDecoration(new MediaGridItemDecoration(spacing));
|
|
recyclerView.setLayoutManager(lm);
|
|
recyclerView.setAdapter(uploadListAdapter);
|
|
|
|
|
|
swipeListRefreshLayout.setOnRefreshListener(this::refresh);
|
|
|
|
loadItems();
|
|
}
|
|
|
|
private void loadItems() {
|
|
uploadListAdapter.loadUploadItemsFromDb();
|
|
|
|
if (uploadListAdapter.getItemCount() > 0) {
|
|
return;
|
|
}
|
|
|
|
swipeListRefreshLayout.setVisibility(View.VISIBLE);
|
|
swipeListRefreshLayout.setRefreshing(false);
|
|
}
|
|
|
|
private void refresh() {
|
|
// scan for missing auto uploads files
|
|
Set<Job> jobs = JobManager.instance().getAllJobsForTag(FilesSyncJob.TAG);
|
|
|
|
if (jobs.isEmpty()) {
|
|
PersistableBundleCompat persistableBundleCompat = new PersistableBundleCompat();
|
|
persistableBundleCompat.putBoolean(FilesSyncJob.OVERRIDE_POWER_SAVING, true);
|
|
new JobRequest.Builder(FilesSyncJob.TAG)
|
|
.setExact(1_000L)
|
|
.setUpdateCurrent(false)
|
|
.setExtras(persistableBundleCompat)
|
|
.build()
|
|
.schedule();
|
|
}
|
|
|
|
// retry failed uploads
|
|
FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
|
|
new Thread(() -> requester.retryFailedUploads(this, null, uploadsStorageManager,null)).start();
|
|
|
|
// update UI
|
|
uploadListAdapter.loadUploadItemsFromDb();
|
|
swipeListRefreshLayout.setRefreshing(false);
|
|
}
|
|
|
|
@Override
|
|
protected void onResume() {
|
|
Log_OC.v(TAG, "onResume() start");
|
|
super.onResume();
|
|
|
|
setDrawerMenuItemChecked(R.id.nav_uploads);
|
|
|
|
// Listen for upload messages
|
|
uploadMessagesReceiver = new UploadMessagesReceiver();
|
|
IntentFilter uploadIntentFilter = new IntentFilter();
|
|
uploadIntentFilter.addAction(FileUploader.getUploadsAddedMessage());
|
|
uploadIntentFilter.addAction(FileUploader.getUploadStartMessage());
|
|
uploadIntentFilter.addAction(FileUploader.getUploadFinishMessage());
|
|
registerReceiver(uploadMessagesReceiver, uploadIntentFilter);
|
|
|
|
Log_OC.v(TAG, "onResume() end");
|
|
|
|
}
|
|
|
|
@Override
|
|
protected void onPause() {
|
|
Log_OC.v(TAG, "onPause() start");
|
|
if (uploadMessagesReceiver != null) {
|
|
unregisterReceiver(uploadMessagesReceiver);
|
|
uploadMessagesReceiver = null;
|
|
}
|
|
super.onPause();
|
|
Log_OC.v(TAG, "onPause() end");
|
|
}
|
|
|
|
@Override
|
|
public boolean onCreateOptionsMenu(Menu menu) {
|
|
MenuInflater inflater = getMenuInflater();
|
|
inflater.inflate(R.menu.upload_list_menu, menu);
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
boolean retval = true;
|
|
switch (item.getItemId()) {
|
|
case android.R.id.home:
|
|
if (isDrawerOpen()) {
|
|
closeDrawer();
|
|
} else {
|
|
openDrawer();
|
|
}
|
|
break;
|
|
case R.id.action_clear_failed_uploads:
|
|
uploadsStorageManager.clearFailedButNotDelayedUploads();
|
|
uploadListAdapter.loadUploadItemsFromDb();
|
|
break;
|
|
|
|
default:
|
|
retval = super.onOptionsItemSelected(item);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
@Override
|
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
super.onActivityResult(requestCode, resultCode, data);
|
|
if (requestCode == FileActivity.REQUEST_CODE__UPDATE_CREDENTIALS && resultCode == RESULT_OK) {
|
|
FilesSyncHelper.restartJobsIfNeeded(uploadsStorageManager, userAccountManager);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param operation Operation performed.
|
|
* @param result Result of the removal.
|
|
*/
|
|
@Override
|
|
public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
|
|
if (operation instanceof CheckCurrentCredentialsOperation) {
|
|
// Do not call super in this case; more refactoring needed around onRemoteOperationFinish :'(
|
|
getFileOperationsHelper().setOpIdWaitingFor(Long.MAX_VALUE);
|
|
dismissLoadingDialog();
|
|
Account account = (Account) result.getData().get(0);
|
|
if (!result.isSuccess()) {
|
|
requestCredentialsUpdate(this, account);
|
|
|
|
} else {
|
|
// already updated -> just retry!
|
|
FilesSyncHelper.restartJobsIfNeeded(uploadsStorageManager, userAccountManager);
|
|
}
|
|
|
|
} else {
|
|
super.onRemoteOperationFinish(operation, result);
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
protected ServiceConnection newTransferenceServiceConnection() {
|
|
return new UploadListServiceConnection();
|
|
}
|
|
|
|
/**
|
|
* Defines callbacks for service binding, passed to bindService()
|
|
*/
|
|
private class UploadListServiceConnection implements ServiceConnection {
|
|
|
|
@Override
|
|
public void onServiceConnected(ComponentName component, IBinder service) {
|
|
if (service instanceof FileUploaderBinder) {
|
|
if (mUploaderBinder == null) {
|
|
mUploaderBinder = (FileUploaderBinder) service;
|
|
Log_OC.d(TAG, "UploadListActivity connected to Upload service. component: " +
|
|
component + " service: " + service);
|
|
} else {
|
|
Log_OC.d(TAG, "mUploaderBinder already set. mUploaderBinder: " +
|
|
mUploaderBinder + " service:" + service);
|
|
}
|
|
} else {
|
|
Log_OC.d(TAG, "UploadListActivity not connected to Upload service. component: " +
|
|
component + " service: " + service);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onServiceDisconnected(ComponentName component) {
|
|
if (component.equals(new ComponentName(UploadListActivity.this, FileUploader.class))) {
|
|
Log_OC.d(TAG, "UploadListActivity suddenly disconnected from Upload service");
|
|
mUploaderBinder = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Once the file upload has changed its status -> update uploads list view
|
|
*/
|
|
private class UploadMessagesReceiver extends BroadcastReceiver {
|
|
/**
|
|
* {@link BroadcastReceiver} to enable syncing feedback in UI
|
|
*/
|
|
@Override
|
|
public void onReceive(Context context, Intent intent) {
|
|
try {
|
|
uploadListAdapter.loadUploadItemsFromDb();
|
|
} finally {
|
|
if (intent != null) {
|
|
removeStickyBroadcast(intent);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when the ownCloud {@link Account} associated to the Activity was just updated.
|
|
*/
|
|
@Override
|
|
protected void onAccountSet(boolean stateWasRecovered) {
|
|
super.onAccountSet(stateWasRecovered);
|
|
|
|
ThemeUtils.setColoredTitle(getSupportActionBar(), R.string.uploads_view_title, this);
|
|
|
|
if (mAccountWasSet) {
|
|
setAccountInDrawer(getAccount());
|
|
}
|
|
}
|
|
|
|
public void onDestroy() {
|
|
super.onDestroy();
|
|
unbinder.unbind();
|
|
}
|
|
}
|