/** * 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; } }