/* * ownCloud Android client application * * @author Andy Scherzinger * Copyright (C) 2016 ownCloud Inc. *

* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * as published by the Free Software Foundation. *

* 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 General Public License for more details. *

* You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.owncloud.android.ui.activity; import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AccountManagerCallback; import android.accounts.AccountManagerFuture; import android.accounts.OperationCanceledException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; import com.evernote.android.job.JobRequest; import com.evernote.android.job.util.support.PersistableBundleCompat; import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.files.services.FileDownloader; import com.owncloud.android.files.services.FileUploader; import com.owncloud.android.jobs.AccountRemovalJob; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.services.OperationsService; import com.owncloud.android.ui.adapter.AccountListAdapter; import com.owncloud.android.ui.adapter.AccountListItem; import com.owncloud.android.ui.events.AccountRemovedEvent; import com.owncloud.android.ui.helpers.FileOperationsHelper; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.ThemeUtils; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import org.parceler.Parcels; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.DrawableCompat; /** * An Activity that allows the user to manage accounts. */ public class ManageAccountsActivity extends FileActivity implements AccountListAdapter.AccountListAdapterListener, AccountManagerCallback, ComponentsGetter { private static final String TAG = ManageAccountsActivity.class.getSimpleName(); public static final String KEY_ACCOUNT_LIST_CHANGED = "ACCOUNT_LIST_CHANGED"; public static final String KEY_CURRENT_ACCOUNT_CHANGED = "CURRENT_ACCOUNT_CHANGED"; public static final String PENDING_FOR_REMOVAL = "PENDING_FOR_REMOVAL"; private static final String KEY_DISPLAY_NAME = "DISPLAY_NAME"; private static final int KEY_USER_INFO_REQUEST_CODE = 13; private static final int KEY_DELETE_CODE = 101; private static final int SINGLE_ACCOUNT = 1; private static final int MIN_MULTI_ACCOUNT_SIZE = 2; private ListView mListView; private final Handler mHandler = new Handler(); private String mAccountName; private AccountListAdapter mAccountListAdapter; private ServiceConnection mDownloadServiceConnection; private ServiceConnection mUploadServiceConnection; private Set mOriginalAccounts; private String mOriginalCurrentAccount; private Drawable mTintedCheck; private ArbitraryDataProvider arbitraryDataProvider; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mTintedCheck = DrawableCompat.wrap(ContextCompat.getDrawable(this, R.drawable.account_circle_white)); int tint = ThemeUtils.elementColor(this); DrawableCompat.setTint(mTintedCheck, tint); setContentView(R.layout.accounts_layout); mListView = findViewById(R.id.account_list); setupToolbar(); updateActionBarTitleAndHomeButtonByString(getResources().getString(R.string.prefs_manage_accounts)); Account[] accountList = AccountManager.get(this).getAccountsByType(MainApp.getAccountType(this)); mOriginalAccounts = DisplayUtils.toAccountNameSet(Arrays.asList(accountList)); Account currentAccount = AccountUtils.getCurrentOwnCloudAccount(this); if (currentAccount != null) { mOriginalCurrentAccount = currentAccount.name; } setAccount(currentAccount); onAccountSet(false); arbitraryDataProvider = new ArbitraryDataProvider(getContentResolver()); mAccountListAdapter = new AccountListAdapter(this, getAccountListItems(), mTintedCheck); mListView.setAdapter(mAccountListAdapter); final Intent intent = new Intent(this, UserInfoActivity.class); mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { AccountListItem item = mAccountListAdapter.getItem(position); if (item != null && item.isEnabled()) { Account account = item.getAccount(); intent.putExtra(UserInfoActivity.KEY_ACCOUNT, Parcels.wrap(account)); try { OwnCloudAccount oca = new OwnCloudAccount(account, MainApp.getAppContext()); intent.putExtra(KEY_DISPLAY_NAME, oca.getDisplayName()); } catch (com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException e) { Log_OC.d(TAG, "Failed to find NC account"); } startActivityForResult(intent, KEY_USER_INFO_REQUEST_CODE); } } }); initializeComponentGetters(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (resultCode) { case KEY_DELETE_CODE: if (data != null) { Bundle bundle = data.getExtras(); if (bundle.containsKey(UserInfoActivity.KEY_ACCOUNT)) { Account account = Parcels.unwrap(bundle.getParcelable(UserInfoActivity.KEY_ACCOUNT)); mAccountName = account.name; performAccountRemoval(account); } } break; default: break; } } @Override public void onBackPressed() { Intent resultIntent = new Intent(); resultIntent.putExtra(KEY_ACCOUNT_LIST_CHANGED, hasAccountListChanged()); resultIntent.putExtra(KEY_CURRENT_ACCOUNT_CHANGED, hasCurrentAccountChanged()); setResult(RESULT_OK, resultIntent); super.onBackPressed(); } /** * checks the set of actual accounts against the set of original accounts when the activity has been started. * * @return true if account list has changed, false if not */ private boolean hasAccountListChanged() { Account[] accountList = AccountManager.get(this).getAccountsByType(MainApp.getAccountType(this)); ArrayList newList = new ArrayList<>(); for (Account account : accountList) { boolean pendingForRemoval = arbitraryDataProvider.getBooleanValue(account, PENDING_FOR_REMOVAL); if (!pendingForRemoval) { newList.add(account); } } Set actualAccounts = DisplayUtils.toAccountNameSet(newList); return !mOriginalAccounts.equals(actualAccounts); } /** * checks actual current account against current accounts when the activity has been started. * * @return true if account list has changed, false if not */ private boolean hasCurrentAccountChanged() { Account account = AccountUtils.getCurrentOwnCloudAccount(this); if (account == null) { return true; } else { return !account.name.equals(mOriginalCurrentAccount); } } /** * Initialize ComponentsGetters. */ private void initializeComponentGetters() { mDownloadServiceConnection = newTransferenceServiceConnection(); if (mDownloadServiceConnection != null) { bindService(new Intent(this, FileDownloader.class), mDownloadServiceConnection, Context.BIND_AUTO_CREATE); } mUploadServiceConnection = newTransferenceServiceConnection(); if (mUploadServiceConnection != null) { bindService(new Intent(this, FileUploader.class), mUploadServiceConnection, Context.BIND_AUTO_CREATE); } } /** * creates the account list items list including the add-account action in case multiaccount_support is enabled. * * @return list of account list items */ private List getAccountListItems() { Account[] accountList = AccountManager.get(this).getAccountsByType(MainApp.getAccountType(this)); List adapterAccountList = new ArrayList<>(accountList.length); for (Account account : accountList) { boolean pendingForRemoval = arbitraryDataProvider.getBooleanValue(account, PENDING_FOR_REMOVAL); adapterAccountList.add(new AccountListItem(account, !pendingForRemoval)); } // Add Create Account item at the end of account list if multi-account is enabled if (getResources().getBoolean(R.bool.multiaccount_support)) { adapterAccountList.add(new AccountListItem()); } return adapterAccountList; } @Override public boolean onOptionsItemSelected(MenuItem item) { boolean retval = true; switch (item.getItemId()) { case android.R.id.home: onBackPressed(); break; default: retval = super.onOptionsItemSelected(item); break; } return retval; } @Override public void showFirstRunActivity() { Intent firstRunIntent = new Intent(getApplicationContext(), FirstRunActivity.class); firstRunIntent.putExtra(FirstRunActivity.EXTRA_ALLOW_CLOSE, true); startActivity(firstRunIntent); } @Override public void createAccount() { AccountManager am = AccountManager.get(getApplicationContext()); am.addAccount(MainApp.getAccountType(this), null, null, null, this, new AccountManagerCallback() { @Override public void run(AccountManagerFuture future) { if (future != null) { try { Bundle result = future.getResult(); String name = result.getString(AccountManager.KEY_ACCOUNT_NAME); AccountUtils.setCurrentOwnCloudAccount(getApplicationContext(), name); mAccountListAdapter = new AccountListAdapter( ManageAccountsActivity.this, getAccountListItems(), mTintedCheck ); mListView.setAdapter(mAccountListAdapter); runOnUiThread(new Runnable() { @Override public void run() { mAccountListAdapter.notifyDataSetChanged(); } }); } catch (OperationCanceledException e) { Log_OC.d(TAG, "Account creation canceled"); } catch (Exception e) { Log_OC.e(TAG, "Account creation finished in exception: ", e); } } } }, mHandler); } @Subscribe(threadMode = ThreadMode.MAIN) public void onAccountRemovedEvent(AccountRemovedEvent event) { List accountListItemArray = getAccountListItems(); mAccountListAdapter.clear(); mAccountListAdapter.addAll(accountListItemArray); mAccountListAdapter.notifyDataSetChanged(); } @Override public void run(AccountManagerFuture future) { if (future.isDone()) { // after remove account Account account = new Account(mAccountName, MainApp.getAccountType(this)); if (!AccountUtils.exists(account, MainApp.getAppContext())) { // Cancel transfers of the removed account if (mUploaderBinder != null) { mUploaderBinder.cancel(account); } if (mDownloaderBinder != null) { mDownloaderBinder.cancel(account); } } if (AccountUtils.getCurrentOwnCloudAccount(this) == null) { String accountName = ""; Account[] accounts = AccountManager.get(this).getAccountsByType(MainApp.getAccountType(this)); if (accounts.length != 0) { accountName = accounts[0].name; } AccountUtils.setCurrentOwnCloudAccount(this, accountName); } List accountListItemArray = getAccountListItems(); if (accountListItemArray.size() > SINGLE_ACCOUNT) { mAccountListAdapter = new AccountListAdapter(this, accountListItemArray, mTintedCheck); mListView.setAdapter(mAccountListAdapter); } else { onBackPressed(); } } } @Override protected void onDestroy() { if (mDownloadServiceConnection != null) { unbindService(mDownloadServiceConnection); mDownloadServiceConnection = null; } if (mUploadServiceConnection != null) { unbindService(mUploadServiceConnection); mUploadServiceConnection = null; } super.onDestroy(); } public Handler getHandler() { return mHandler; } @Override public FileUploader.FileUploaderBinder getFileUploaderBinder() { return mUploaderBinder; } @Override public OperationsService.OperationsServiceBinder getOperationsServiceBinder() { return null; } @Override public FileDataStorageManager getStorageManager() { return super.getStorageManager(); } @Override public FileOperationsHelper getFileOperationsHelper() { return null; } protected ServiceConnection newTransferenceServiceConnection() { return new ManageAccountsServiceConnection(); } private void performAccountRemoval(Account account) { // disable account in list view for (int i = 0; i < mAccountListAdapter.getCount(); i++) { AccountListItem item = mAccountListAdapter.getItem(i); if (item != null && item.getAccount().equals(account)) { item.setEnabled(false); break; } mAccountListAdapter.notifyDataSetChanged(); } // store pending account removal ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(getContentResolver()); arbitraryDataProvider.storeOrUpdateKeyValue(account.name, PENDING_FOR_REMOVAL, String.valueOf(true)); // Cancel transfers if (mUploaderBinder != null) { mUploaderBinder.cancel(account); } if (mDownloaderBinder != null) { mDownloaderBinder.cancel(account); } // schedule job PersistableBundleCompat bundle = new PersistableBundleCompat(); bundle.putString(AccountRemovalJob.ACCOUNT, account.name); new JobRequest.Builder(AccountRemovalJob.TAG) .startNow() .setExtras(bundle) .setUpdateCurrent(false) .build() .schedule(); // immediately select a new account Account[] accounts = AccountManager.get(this).getAccountsByType(MainApp.getAccountType(this)); String newAccountName = ""; for (Account acc: accounts) { if (!account.name.equalsIgnoreCase(acc.name)) { newAccountName = acc.name; break; } } if (newAccountName.isEmpty()) { Log_OC.d(TAG, "new account set to null"); AccountUtils.resetOwnCloudAccount(this); } else { Log_OC.d(TAG, "new account set to: " + newAccountName); AccountUtils.setCurrentOwnCloudAccount(this, newAccountName); } // only one to be (deleted) account remaining if (accounts.length < MIN_MULTI_ACCOUNT_SIZE) { Intent resultIntent = new Intent(); resultIntent.putExtra(KEY_ACCOUNT_LIST_CHANGED, true); resultIntent.putExtra(KEY_CURRENT_ACCOUNT_CHANGED, true); setResult(RESULT_OK, resultIntent); super.onBackPressed(); } } /** * Defines callbacks for service binding, passed to bindService() */ private class ManageAccountsServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName component, IBinder service) { if (component.equals(new ComponentName(ManageAccountsActivity.this, FileDownloader.class))) { mDownloaderBinder = (FileDownloader.FileDownloaderBinder) service; } else if (component.equals(new ComponentName(ManageAccountsActivity.this, FileUploader.class))) { Log_OC.d(TAG, "Upload service connected"); mUploaderBinder = (FileUploader.FileUploaderBinder) service; } } @Override public void onServiceDisconnected(ComponentName component) { if (component.equals(new ComponentName(ManageAccountsActivity.this, FileDownloader.class))) { Log_OC.d(TAG, "Download service suddenly disconnected"); mDownloaderBinder = null; } else if (component.equals(new ComponentName(ManageAccountsActivity.this, FileUploader.class))) { Log_OC.d(TAG, "Upload service suddenly disconnected"); mUploaderBinder = null; } } } }