/**
* 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.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
import android.support.design.widget.NavigationView;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.authentication.AccountUtils;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.status.OCCapability;
import com.owncloud.android.ui.TextDrawable;
import com.owncloud.android.utils.BitmapUtils;
/**
* Base class to handle setup of the drawer implementation.
*/
public abstract class DrawerActivity extends ToolbarActivity {
private static final String TAG = DrawerActivity.class.getSimpleName();
private static final String KEY_IS_ACCOUNT_CHOOSER_ACTIVE = "IS_ACCOUNT_CHOOSER_ACTIVE";
private static final int ACTION_MANAGE_ACCOUNTS = 101;
/**
* Reference to the drawer layout.
*/
private DrawerLayout mDrawerLayout;
/**
* Reference to the drawer toggle.
*/
private ActionBarDrawerToggle mDrawerToggle;
/**
* Reference to the navigation view.
*/
private NavigationView mNavigationView;
/**
* Reference to the account chooser toogle.
*/
private ImageView mAccountChooserToggle;
/**
* ownCloud {@link Account} where the main {@link OCFile} handled by the activity is located.
*/
private Account mCurrentAccount;
/**
* Flag to signal if the account chooser is active.
*/
private boolean mIsAccountChooserActive;
/**
* Flag to signal that the activity will is finishing to enforce the creation of an ownCloud {@link Account}.
*/
private boolean mRedirectingToSetupAccount = false;
/**
* Flag to signal when the value of mAccount was set.
*/
protected boolean mAccountWasSet;
/**
* Flag to signal when the value of mAccount was restored from a saved state.
*/
protected boolean mAccountWasRestored;
/**
* Capabilites of the server where {@link #mCurrentAccount} lives.
*/
private OCCapability mCapabilities;
/**
* Access point to the cached database for the current ownCloud {@link Account}.
*/
private FileDataStorageManager mStorageManager = null;
/**
* Initializes the drawer and its content.
* This method needs to be called after the content view has been set.
*/
protected void setupDrawer() {
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mNavigationView = (NavigationView) findViewById(R.id.nav_view);
if (mNavigationView != null) {
setupDrawerContent(mNavigationView);
mAccountChooserToggle = (ImageView) findNavigationViewChildById(R.id.drawer_account_chooser_toogle);
mAccountChooserToggle.setImageResource(R.drawable.ic_down);
mIsAccountChooserActive = false;
findNavigationViewChildById(R.id.drawer_active_user)
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
toggleAccountList();
}
});
}
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, R.string.drawer_open, R.string.drawer_close) {
/** Called when a drawer has settled in a completely closed state. */
public void onDrawerClosed(View view) {
super.onDrawerClosed(view);
// standard behavior of drawer is to switch to the standard menu on closing
if (mIsAccountChooserActive) {
toggleAccountList();
}
updateActionBarTitleAndHomeButton(null);
invalidateOptionsMenu();
}
/** Called when a drawer has settled in a completely open state. */
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
getSupportActionBar().setTitle(R.string.app_name);
mDrawerToggle.setDrawerIndicatorEnabled(true);
invalidateOptionsMenu();
}
};
// Set the drawer toggle as the DrawerListener
mDrawerLayout.setDrawerListener(mDrawerToggle);
mDrawerToggle.setDrawerIndicatorEnabled(false);
}
/**
* setup drawer content, basically setting the item selected listener.
*
* @param navigationView the drawers navigation view
*/
protected void setupDrawerContent(NavigationView navigationView) {
navigationView.setNavigationItemSelectedListener(
new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(MenuItem menuItem) {
mDrawerLayout.closeDrawers();
switch (menuItem.getItemId()) {
case R.id.nav_all_files:
menuItem.setChecked(true);
allFilesOption();
break;
case R.id.nav_settings:
Intent settingsIntent = new Intent(getApplicationContext(),
Preferences.class);
startActivity(settingsIntent);
break;
case R.id.drawer_menu_account_add:
createAccount();
break;
case R.id.drawer_menu_account_manage:
Intent manageAccountsIntent = new Intent(getApplicationContext(),
ManageAccountsActivity.class);
startActivityForResult(manageAccountsIntent, ACTION_MANAGE_ACCOUNTS);
break;
case Menu.NONE:
// account clicked
AccountUtils.setCurrentOwnCloudAccount(
getApplicationContext(), menuItem.getTitle().toString());
restart();
default:
Log_OC.i(TAG, "Unknown drawer menu item clicked: " + menuItem.getTitle());
}
return true;
}
});
// handle correct state
if (mIsAccountChooserActive) {
mNavigationView.getMenu().setGroupVisible(R.id.drawer_menu_accounts, true);
} else {
mNavigationView.getMenu().setGroupVisible(R.id.drawer_menu_accounts, false);
}
}
/**
* checks if the drawer exists and is opened.
*
* @return true
if the drawer is open, else false
*/
public boolean isDrawerOpen() {
return mDrawerLayout != null && mDrawerLayout.isDrawerOpen(GravityCompat.START);
}
/**
* closes the drawer.
*/
public void closeDrawer() {
if (mDrawerLayout != null) {
mDrawerLayout.closeDrawer(GravityCompat.START);
}
}
/**
* opens the drawer.
*/
public void openDrawer() {
if (mDrawerLayout != null) {
mDrawerLayout.openDrawer(GravityCompat.START);
}
}
/**
* Enable or disable interaction with all drawers.
*
* @param lockMode The new lock mode for the given drawer. One of {@link DrawerLayout#LOCK_MODE_UNLOCKED},
* {@link DrawerLayout#LOCK_MODE_LOCKED_CLOSED} or {@link DrawerLayout#LOCK_MODE_LOCKED_OPEN}.
*/
public void setDrawerLockMode(int lockMode) {
if (mDrawerLayout != null) {
mDrawerLayout.setDrawerLockMode(lockMode);
}
}
/**
* Enable or disable the drawer indicator.
*
* @param enable true
to enable, false
to disable
*/
public void setDrawerIndicatorEnabled(boolean enable) {
if (mDrawerToggle != null) {
mDrawerToggle.setDrawerIndicatorEnabled(enable);
}
}
/**
* updates the account list in the drawer.
*/
public void updateAccountList() {
Account[] accounts = AccountManager.get(this).getAccountsByType(MainApp.getAccountType());
if(accounts.length > 0) {
repopulateAccountList(accounts);
setUsernameInDrawer(AccountUtils.getCurrentOwnCloudAccount(this).name);
}
}
/**
* re-populates the account list.
*
* @param accounts list of accounts
*/
private void repopulateAccountList(Account[] accounts) {
// remove all accounts from list
mNavigationView.getMenu().removeGroup(R.id.drawer_menu_accounts);
// add all accounts to list
for (int i = 0; i < accounts.length; i++) {
try {
int[] rgb = BitmapUtils.calculateRGB(accounts[i].name);
TextDrawable icon = new TextDrawable(accounts[i].name.substring(0, 1).toUpperCase()
, rgb[0], rgb[1], rgb[2]);
mNavigationView.getMenu().add(R.id.drawer_menu_accounts, Menu.NONE, 1, accounts[i].name).setIcon(icon);
} catch (Exception e) {
Log_OC.e(TAG, "Error calculating RGB value for account menu item.", e);
mNavigationView.getMenu().add(R.id.drawer_menu_accounts, Menu.NONE, 1, accounts[i].name).setIcon(R
.drawable.ic_account_circle);
}
}
// re-add add-account and manage-accounts
mNavigationView.getMenu().add(R.id.drawer_menu_accounts, R.id.drawer_menu_account_add, 2,
getResources().getString(R.string.prefs_add_account)).setIcon(R.drawable.ic_account_plus);
mNavigationView.getMenu().add(R.id.drawer_menu_accounts, R.id.drawer_menu_account_manage, 2,
getResources().getString(R.string.drawer_manage_accounts)).setIcon(R.drawable.ic_settings);
// adding sets menu group back to visible, so safety check and setting invisible
showMenu();
}
/**
* Method that gets called on drawer menu click for 'All Files'.
*/
public abstract void allFilesOption();
/**
* Updates title bar and home buttons (state and icon).
*
* Assumes that navigation drawer is NOT visible.
*/
protected void updateActionBarTitleAndHomeButton(OCFile chosenFile) {
super.updateActionBarTitleAndHomeButton(chosenFile);
/// set home button properties
mDrawerToggle.setDrawerIndicatorEnabled(isRoot(chosenFile));
}
/**
* sets the given account name in the drawer in case the drawer is available. The account name is shortened
* beginning from the @-sign in the username.
*
* @param accountName the account to be set in the drawer
*/
protected void setUsernameInDrawer(String accountName) {
if (mDrawerLayout != null && accountName != null) {
TextView username = (TextView) ((NavigationView) findViewById(R.id.nav_view))
.getHeaderView(0).findViewById(R.id.drawer_username);
TextView usernameFull = (TextView) ((NavigationView) findViewById(R.id.nav_view))
.getHeaderView(0).findViewById(R.id.drawer_username_full);
usernameFull.setText(accountName);
int lastAtPos = accountName.lastIndexOf("@");
username.setText(accountName.substring(0, lastAtPos));
ImageView userIcon = (ImageView) ((NavigationView) findViewById(R.id.nav_view))
.getHeaderView(0).findViewById(R.id.drawer_usericon);
try {
int[] rgb = BitmapUtils.calculateRGB(accountName);
TextDrawable icon = new TextDrawable(
accountName.substring(0, 1).toUpperCase(), rgb[0], rgb[1], rgb[2]);
userIcon.setImageDrawable(icon);
} catch (Exception e) {
Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e);
userIcon.setImageResource(R.drawable.ic_account_circle);
}
}
}
/**
* Toggle between standard menu and account list including saving the state.
*/
private void toggleAccountList() {
mIsAccountChooserActive = !mIsAccountChooserActive;
showMenu();
}
/**
* depending on the #mIsAccountChooserActive flag shows the account chooser or the standard menu.
*/
private void showMenu() {
if (mIsAccountChooserActive) {
mAccountChooserToggle.setImageResource(R.drawable.ic_up);
mNavigationView.getMenu().setGroupVisible(R.id.drawer_menu_accounts, true);
mNavigationView.getMenu().setGroupVisible(R.id.drawer_menu_standard, false);
} else {
mAccountChooserToggle.setImageResource(R.drawable.ic_down);
mNavigationView.getMenu().setGroupVisible(R.id.drawer_menu_accounts, false);
mNavigationView.getMenu().setGroupVisible(R.id.drawer_menu_standard, true);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mIsAccountChooserActive = savedInstanceState.getBoolean(KEY_IS_ACCOUNT_CHOOSER_ACTIVE, false);
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(KEY_IS_ACCOUNT_CHOOSER_ACTIVE, mIsAccountChooserActive);
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
mIsAccountChooserActive = savedInstanceState.getBoolean(KEY_IS_ACCOUNT_CHOOSER_ACTIVE, false);
// (re-)setup drawer state
showMenu();
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Sync the toggle state after onRestoreInstanceState has occurred.
if (mDrawerToggle != null) {
mDrawerToggle.syncState();
if (isDrawerOpen()) {
getSupportActionBar().setTitle(R.string.app_name);
mDrawerToggle.setDrawerIndicatorEnabled(true);
}
}
updateAccountList();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (mDrawerToggle != null) {
mDrawerToggle.onConfigurationChanged(newConfig);
}
}
@Override
public void onBackPressed() {
if (isDrawerOpen()) {
closeDrawer();
return;
}
super.onBackPressed();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// update Account list and active account if Manage Account activity replies with
// - ACCOUNT_LIST_CHANGED = true
// - RESULT_OK
if (requestCode == ACTION_MANAGE_ACCOUNTS
&& resultCode == RESULT_OK
&& data.getBooleanExtra(ManageAccountsActivity.KEY_ACCOUNT_LIST_CHANGED, false)) {
// current account has changed
if(data.getBooleanExtra(ManageAccountsActivity.KEY_CURRENT_ACCOUNT_CHANGED, false)) {
mCurrentAccount = AccountUtils.getCurrentOwnCloudAccount(this);
restart();
} else {
updateAccountList();
}
}
}
/**
* Finds a view that was identified by the id attribute from the drawer header.
*
* @param id the view's id
* @return The view if found or null
otherwise.
*/
private View findNavigationViewChildById(int id) {
return ((NavigationView) findViewById(R.id.nav_view)).getHeaderView(0).findViewById(id);
}
// TODO call on current account changed
public void restart() {
Intent i = new Intent(this, FileDisplayActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);
}
/**
* Sets and validates the ownCloud {@link Account} associated to the Activity.
*
* If not valid, tries to swap it for other valid and existing ownCloud {@link Account}.
*
* POSTCONDITION: updates {@link #mAccountWasSet} and {@link #mAccountWasRestored}.
*
* @param account New {@link Account} to set.
* @param savedAccount When 'true', account was retrieved from a saved instance state.
*/
protected void setAccount(Account account, boolean savedAccount) {
Account oldAccount = mCurrentAccount;
boolean validAccount =
(account != null && AccountUtils.setCurrentOwnCloudAccount(getApplicationContext(),
account.name));
if (validAccount) {
mCurrentAccount = account;
mAccountWasSet = true;
mAccountWasRestored = (savedAccount || mCurrentAccount.equals(oldAccount));
} else {
swapToDefaultAccount();
}
}
/**
* Tries to swap the current ownCloud {@link Account} for other valid and existing.
*
* If no valid ownCloud {@link Account} exists, the the user is requested
* to create a new ownCloud {@link Account}.
*
* POSTCONDITION: updates {@link #mAccountWasSet} and {@link #mAccountWasRestored}.
*/
protected void swapToDefaultAccount() {
// default to the most recently used account
Account newAccount = AccountUtils.getCurrentOwnCloudAccount(getApplicationContext());
if (newAccount == null) {
/// no account available: force account creation
createAccount();
mRedirectingToSetupAccount = true;
mAccountWasSet = false;
mAccountWasRestored = false;
} else {
mAccountWasSet = true;
mAccountWasRestored = (newAccount.equals(mCurrentAccount));
mCurrentAccount = newAccount;
}
}
/**
* Launches the account creation activity. To use when no ownCloud account is available.
*/
private void createAccount() {
AccountManager am = AccountManager.get(getApplicationContext());
am.addAccount(MainApp.getAccountType(),
null,
null,
null,
this,
new AccountCreationCallback(),
new Handler());
}
/**
* Helper class handling a callback from the {@link AccountManager} after the creation of
* a new ownCloud {@link Account} finished, successfully or not.
*
* At this moment, only called after the creation of the first account.
*/
public class AccountCreationCallback implements AccountManagerCallback {
@Override
public void run(AccountManagerFuture future) {
DrawerActivity.this.mRedirectingToSetupAccount = false;
boolean accountWasSet = false;
if (future != null) {
try {
Bundle result;
result = future.getResult();
String name = result.getString(AccountManager.KEY_ACCOUNT_NAME);
String type = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
if (AccountUtils.setCurrentOwnCloudAccount(getApplicationContext(), name)) {
setAccount(new Account(name, type), false);
accountWasSet = true;
}
DrawerActivity.this.updateAccountList();
DrawerActivity.this.restart();
} catch (OperationCanceledException e) {
Log_OC.d(TAG, "Account creation canceled");
} catch (Exception e) {
Log_OC.e(TAG, "Account creation finished in exception: ", e);
}
} else {
Log_OC.e(TAG, "Account creation callback with null bundle");
}
if (!accountWasSet) {
moveTaskToBack(true);
}
}
}
/**
* Called when the ownCloud {@link Account} associated to the Activity was just updated.
*
* Child classes must grant that state depending on the {@link Account} is updated.
*/
protected void onAccountSet(boolean stateWasRecovered) {
if (getAccount() != null) {
mStorageManager = new FileDataStorageManager(getAccount(), getContentResolver());
mCapabilities = mStorageManager.getCapability(mCurrentAccount.name);
} else {
Log_OC.wtf(TAG, "onAccountChanged was called with NULL account associated!");
}
}
protected void setAccount(Account account) {
mCurrentAccount = account;
}
/**
* Getter for the capabilities of the server where the current OC account lives.
*
* @return Capabilities of the server where the current OC account lives. Null if the account is not
* set yet.
*/
public OCCapability getCapabilities() {
return mCapabilities;
}
/**
* Getter for the ownCloud {@link Account} where the main {@link OCFile} handled by the activity
* is located.
*
* @return OwnCloud {@link Account} where the main {@link OCFile} handled by the activity
* is located.
*/
public Account getAccount() {
return mCurrentAccount;
}
@Override
protected void onStart() {
super.onStart();
if (mAccountWasSet) {
onAccountSet(mAccountWasRestored);
}
}
/**
* @return 'True' when the Activity is finishing to enforce the setup of a new account.
*/
protected boolean isRedirectingToSetupAccount() {
return mRedirectingToSetupAccount;
}
public FileDataStorageManager getStorageManager() {
return mStorageManager;
}
}