com.weebly.opus1269.copyeverywhere.ui.main.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.weebly.opus1269.copyeverywhere.ui.main.MainActivity.java

Source

/*
 * Copyright 2016 Michael A Updike
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *
 */

package com.weebly.opus1269.copyeverywhere.ui.main;

import android.app.SearchManager;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.PorterDuff;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.NavigationView;
import android.support.design.widget.Snackbar;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.NavUtils;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.weebly.opus1269.copyeverywhere.R;
import com.weebly.opus1269.copyeverywhere.helpers.AppUtils;
import com.weebly.opus1269.copyeverywhere.model.ClipContentProvider;
import com.weebly.opus1269.copyeverywhere.model.ClipContract;
import com.weebly.opus1269.copyeverywhere.model.ClipItem;
import com.weebly.opus1269.copyeverywhere.model.Device;
import com.weebly.opus1269.copyeverywhere.model.Devices;
import com.weebly.opus1269.copyeverywhere.services.ClipboardWatcherService;
import com.weebly.opus1269.copyeverywhere.ui.clipviewer.ClipViewerActivity;
import com.weebly.opus1269.copyeverywhere.ui.clipviewer.ClipViewerFragment;
import com.weebly.opus1269.copyeverywhere.ui.devices.DevicesActivity;
import com.weebly.opus1269.copyeverywhere.ui.settings.Prefs;
import com.weebly.opus1269.copyeverywhere.ui.settings.SettingsActivity;
import com.weebly.opus1269.copyeverywhere.ui.shared.MenuTint;
import com.weebly.opus1269.copyeverywhere.ui.signin.SignInActivity;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.Collator;

@SuppressWarnings({ "ConstantConditions", "OverlyComplexClass" })
public class MainActivity extends AppCompatActivity
        implements ClipViewerFragment.OnClipChanged, NavigationView.OnNavigationItemSelectedListener,
        View.OnLayoutChangeListener, LoaderManager.LoaderCallbacks<Cursor>, View.OnClickListener,
        SearchView.OnQueryTextListener, SearchView.OnCloseListener, DeleteDialogFragment.DeleteDialogListener {

    private static final String TAG = "MainActivity";

    private static final String STATE_POS = "pos";
    private static final String STATE_ITEM_ID = "item_id";
    private static final String STATE_QUERY_STRING = "query";

    // Adapter being used to display the list's data
    private ClipCursorAdapter mAdapter = null;

    // AppBar menu
    private Menu mMenu = null;

    // items from last delete operation
    private ContentValues[] mUndoItems = null;

    /**
     * saved preferences
     */

    // AppBar setting for fav filter
    private Boolean mFavFilter = false;

    /**
     * saved instance state
     */

    // AppBar search string
    private String mQueryString = "";

    // The currently selected position in the list
    private int mSelectedPos = 0;

    // The database _ID of the selection list item
    private long mSelectedItemID = -1L;

    ///////////////////////////////////////////////////////////////////////////
    // Superclass overrides
    ///////////////////////////////////////////////////////////////////////////

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Check whether we're recreating a previously destroyed instance
        if (savedInstanceState != null) {
            // Restore value of members from saved state
            mSelectedPos = savedInstanceState.getInt(STATE_POS);
            mSelectedItemID = savedInstanceState.getLong(STATE_ITEM_ID);
            mQueryString = savedInstanceState.getString(STATE_QUERY_STRING);
        }

        // get preferences
        mFavFilter = Prefs.isFavFilter();

        // start if needed
        ClipboardWatcherService.startService();

        setContentView(R.layout.activity_main);

        final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        setupRecyclerView();

        if (AppUtils.isDualPane()) {
            // create the clip viewer for the two pane option
            final ClipViewerFragment fragment = ClipViewerFragment.newInstance(new ClipItem(), "");
            getSupportFragmentManager().beginTransaction().replace(R.id.clip_viewer_container, fragment).commit();
        }

        // Prepare the loader. Either re-connect with an existing one, or start a new one.
        //noinspection ThisEscapedInObjectConstruction
        getSupportLoaderManager().initLoader(0, null, this);

        final FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.hide();

        final DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        final ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar,
                R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawer.addDrawerListener(toggle);
        toggle.syncState();

        final NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
        navigationView.setNavigationItemSelectedListener(this);

        setTitle((ClipItem) null);

        handleIntent();
    }

    @Override
    protected void onStart() {
        super.onStart();

        if (!Prefs.isLoggedIn() && !Prefs.isSkipSignin()) {
            // Display sigin unless user explicitly skipped it once
            showSignIn();
        }
    }

    @Override
    protected void onPause() {
        // save persistent state
        Prefs.setFavFilter(mFavFilter);

        mUndoItems = null;

        super.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        setupNavigationView();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        mAdapter = null;
        mMenu = null;
        mUndoItems = null;
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        // Save the current selected state
        outState.putInt(STATE_POS, mSelectedPos);
        outState.putLong(STATE_ITEM_ID, mSelectedItemID);
        outState.putString(STATE_QUERY_STRING, mQueryString);

        super.onSaveInstanceState(outState);
    }

    @Override
    public void onBackPressed() {
        final DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        if (drawer.isDrawerOpen(GravityCompat.START)) {
            drawer.closeDrawer(GravityCompat.START);
        } else {
            super.onBackPressed();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);

        mMenu = menu;

        final int color = ContextCompat.getColor(this, R.color.icons);
        final int alpha = 255;
        MenuTint.on(menu).setMenuItemIconColor(color).setMenuItemIconAlpha(alpha).apply(this);

        setupSearch();

        setPrefFavFilter(mFavFilter, false);

        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        boolean processed = true;

        final int id = item.getItemId();
        switch (id) {
        case R.id.home:
            NavUtils.navigateUpFromSameTask(this);
            setQueryString("");
            break;
        case R.id.action_fav_filter:
            setPrefFavFilter(!mFavFilter, true);
            break;
        case R.id.action_delete:
            showDeleteDialog();
            break;
        default:
            processed = false;
            break;
        }

        return processed || super.onOptionsItemSelected(item);
    }

    ///////////////////////////////////////////////////////////////////////////
    // Implement ClipViewerFragment.OnClipChanged
    ///////////////////////////////////////////////////////////////////////////

    @Override
    public void onClipChanged(ClipItem clipItem) {
        setTitle(clipItem);

        final FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        if (fab != null) {
            if (TextUtils.isEmpty(clipItem.getText())) {
                fab.hide();
            } else {
                fab.show();
            }
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // Implement NavigationView.OnNavigationItemSelectedListener
    ///////////////////////////////////////////////////////////////////////////

    @Override
    public boolean onNavigationItemSelected(MenuItem item) {
        // Handle navigation view item clicks here.
        final int id = item.getItemId();

        switch (id) {
        case R.id.nav_manage:
            showSignIn();
            break;
        case R.id.nav_devices:
            showDevices();
            break;
        case R.id.nav_settings:
            showSettings();
            break;
        default:
            break;
        }

        final DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        drawer.closeDrawer(GravityCompat.START);

        return true;
    }

    ///////////////////////////////////////////////////////////////////////////
    // Implement LoaderManager.LoaderCallbacks<Cursor>
    ///////////////////////////////////////////////////////////////////////////

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // Retrieve all columns
        final String[] projection = ClipContract.Clip.FULL_PROJECTION;

        String selection = "(" + "(" + ClipContract.Clip.COL_TEXT + " NOTNULL) AND (" + ClipContract.Clip.COL_TEXT
                + " != '' )";

        if (mFavFilter) {
            // filter by favorite setting selected
            selection = selection + " AND (" + ClipContract.Clip.COL_FAV + " == 1 )";
        }

        String[] selectionArgs = null;
        if (!TextUtils.isEmpty(mQueryString)) {
            // filter by search query
            selection = selection + " AND (" + ClipContract.Clip.COL_TEXT + " LIKE ? )";
            selectionArgs = new String[1];
            selectionArgs[0] = "%" + mQueryString + "%";

        }
        selection = selection + ")";

        // Now create and return a CursorLoader that will take care of
        // creating a Cursor for the data being displayed.
        return new CursorLoader(this, ClipContract.Clip.CONTENT_URI, projection, selection, selectionArgs,
                ClipContract.Clip.DEFAULT_SORT_ORDER);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        // Swap the new cursor in.  (The framework will take care of closing the
        // old cursor once we return.)
        mAdapter.swapCursor(data);

        if (data == null) {
            return;
        }

        if (AppUtils.isDualPane()) {
            // Update the selected item and ClipViewer text.
            // Can't create a new fragment here.
            // see: http://goo.gl/IFQkPc
            if (data.getCount() == 0) {
                setSelectedItemID(-1L);
                showClipViewer(new ClipItem());
            } else {
                int pos;
                if (mSelectedItemID == -1L) {
                    pos = mSelectedPos;
                } else {
                    pos = mAdapter.getPosFromItemID(mSelectedItemID);
                }
                pos = Math.max(0, pos);
                data.moveToPosition(pos);
                final int index = data.getColumnIndex(ClipContract.Clip._ID);
                setSelectedItemID(data.getLong(index));
                showClipViewer(new ClipItem(data));
            }
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        // This is called when the last Cursor provided to onLoadFinished()
        // above is about to be closed.  We need to make sure we are no
        // longer using it.
        mAdapter.swapCursor(null);
    }

    ///////////////////////////////////////////////////////////////////////////
    // Implement SearchView listeners
    ///////////////////////////////////////////////////////////////////////////

    @Override
    public boolean onQueryTextSubmit(String query) {
        setQueryString(query);
        return true;
    }

    @Override
    public boolean onQueryTextChange(String newText) {
        setQueryString(newText);
        return true;
    }

    @Override
    public boolean onClose() {
        setQueryString("");
        return false;
    }

    ///////////////////////////////////////////////////////////////////////////
    // Implement DeleteDialogFragment.DeleteDialogListener
    ///////////////////////////////////////////////////////////////////////////

    @Override
    public void onDialogPositiveClick(Boolean deleteFavs) {
        // save items for undo
        mUndoItems = ClipContentProvider.getAll(this, deleteFavs);

        final int nRows = ClipContentProvider.deleteAll(this, deleteFavs);

        String message = nRows + getResources().getString(R.string.items_deleted);
        switch (nRows) {
        case 0:
            message = getResources().getString(R.string.item_delete_empty);
            break;
        case 1:
            message = nRows + getResources().getString(R.string.item_deleted);
            break;
        default:
            break;
        }
        final Snackbar snack = Snackbar.make(findViewById(R.id.fab), message, Snackbar.LENGTH_LONG);

        if (nRows > 0) {
            snack.setAction("UNDO", new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    ClipContentProvider.insert(MainActivity.this, mUndoItems);
                }
            }).setCallback(new Snackbar.Callback() {

                @Override
                public void onDismissed(Snackbar snackbar, int event) {
                    mUndoItems = null;
                }

                @Override
                public void onShown(Snackbar snackbar) {
                }
            });
        }

        snack.show();
    }

    ///////////////////////////////////////////////////////////////////////////
    // Implement View.OnClickListener
    ///////////////////////////////////////////////////////////////////////////

    @Override
    public void onClick(View v) {
        final ClipCursorAdapter.ClipViewHolder holder;

        final int id = v.getId();
        switch (id) {
        case R.id.clipRow:
            holder = (ClipCursorAdapter.ClipViewHolder) v.getTag();
            onItemViewClicked(holder);
            break;
        case R.id.favCheckBox:
            holder = (ClipCursorAdapter.ClipViewHolder) v.getTag();
            onFavClicked(holder);
            break;
        case R.id.copyButton:
            holder = (ClipCursorAdapter.ClipViewHolder) v.getTag();
            onCopyClicked(holder);
            break;
        case R.id.navHeader:
            onNavHeaderClicked();
            break;
        default:
            break;
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // Implement View.OnLayoutChangeListener
    ///////////////////////////////////////////////////////////////////////////

    /**
     * Set NavigationView header aspect ratio to 16:9
     */
    @Override
    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop,
            int oldRight, int oldBottom) {
        final NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
        if (navigationView == null) {
            return;
        }

        if (v.equals(navigationView)) {
            final int oldWidth = oldRight - oldLeft;
            final int width = right - left;
            final View hView = navigationView.getHeaderView(0);
            if ((hView != null) && (oldWidth != width)) {
                hView.getLayoutParams().height = Math.round((9.0F / 16.0F) * width);
            }
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // Public methods
    ///////////////////////////////////////////////////////////////////////////

    public int getSelectedPos() {
        return mSelectedPos;
    }

    public void setSelectedPos(int position) {
        if (mSelectedPos == position) {
            return;
        }

        if (position < 0) {
            mSelectedPos = -1;
            mSelectedItemID = -1L;
        } else {
            mAdapter.notifyItemChanged(mSelectedPos);
            mSelectedPos = position;
            mAdapter.notifyItemChanged(mSelectedPos);
        }
    }

    public void setSelectedItemID(long itemID) {
        mSelectedItemID = itemID;
        setSelectedPos(mAdapter.getPosFromItemID(mSelectedItemID));
    }

    ///////////////////////////////////////////////////////////////////////////
    // Private methods
    ///////////////////////////////////////////////////////////////////////////

    private void onItemViewClicked(ClipCursorAdapter.ClipViewHolder holder) {
        setSelectedItemID(holder.itemID);
        showClipViewer(holder.clipItem);
    }

    private void onFavClicked(ClipCursorAdapter.ClipViewHolder holder) {
        final boolean checked = holder.favCheckBox.isChecked();
        final long fav = checked ? 1 : 0;
        final Uri uri = ContentUris.withAppendedId(ClipContract.Clip.CONTENT_URI, holder.itemID);
        final ContentValues cv = new ContentValues();

        holder.clipItem.setFav(checked);

        cv.put(ClipContract.Clip.COL_FAV, fav);
        getContentResolver().update(uri, cv, null, null);
    }

    private void onCopyClicked(ClipCursorAdapter.ClipViewHolder holder) {
        holder.clipItem.setRemote(false);
        holder.clipItem.setDevice(Device.myName());
        holder.clipItem.copyToClipboard();
        setTitle(holder.clipItem);
        Snackbar.make(holder.itemView, R.string.clipboard_copy, Snackbar.LENGTH_SHORT).show();
    }

    private void onNavHeaderClicked() {
        final DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        if (drawer != null) {
            drawer.closeDrawer(GravityCompat.START);
        }
        showSignIn();
    }

    /**
     * Process intents we know about
     */
    @SuppressWarnings("CallToStringEquals")
    private void handleIntent() {
        final Intent intent = getIntent();
        final String action = intent.getAction();
        final String type = intent.getType();

        if (Intent.ACTION_SEND.equals(action) && (type != null)) {
            if (ClipItem.TEXT_PLAIN.equals(type)) {
                final String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
                if (sharedText != null) {
                    final ClipItem item = new ClipItem(sharedText);
                    ClipContentProvider.insert(this, item);
                    showClipViewer(item);
                }
            }
        } else if (intent.hasExtra(ClipItem.INTENT_EXTRA_CLIP_ITEM)) {
            final ClipItem item = (ClipItem) intent.getSerializableExtra(ClipItem.INTENT_EXTRA_CLIP_ITEM);
            intent.removeExtra(ClipItem.INTENT_EXTRA_CLIP_ITEM);
            showClipViewer(item);
        } else if (intent.hasExtra(Devices.INTENT_FILTER)) {
            showDevices();
        }
    }

    /**
     * Display the given {@link ClipItem} in the {@link ClipViewerFragment}
     *
     * @param clipItem item to display
     */
    private void showClipViewer(ClipItem clipItem) {
        if (AppUtils.isDualPane()) {
            final ClipViewerFragment fragment = getClipViewerFragment();
            if (fragment != null) {
                fragment.setClipItem(clipItem);
                fragment.setHighlightText(mQueryString);
            }
        } else {
            final Intent intent = new Intent(this, ClipViewerActivity.class);
            intent.putExtra(ClipViewerFragment.ARG_CLIP_ITEM, clipItem);
            intent.putExtra(ClipViewerFragment.ARG_HIGHLIGHT, mQueryString);
            startActivity(intent);
        }
    }

    private void showDeleteDialog() {
        final DialogFragment dialog = new DeleteDialogFragment();
        dialog.show(getSupportFragmentManager(), "DeleteDialogFragment");
    }

    /**
     * Start the activity for managing SignIn
     */
    private void showSignIn() {
        final Intent intent = new Intent(this, SignInActivity.class);
        startActivity(intent);
    }

    /**
     * Start the activity for managing Devices
     */
    private void showDevices() {
        final Intent intent = new Intent(this, DevicesActivity.class);
        startActivity(intent);
    }

    /**
     * Start the activity for managing app preferences
     */
    private void showSettings() {
        final Intent intent = new Intent(this, SettingsActivity.class);
        startActivity(intent);
    }

    /**
     * Initialize the NavigationView
     *
     */
    private void setupNavigationView() {
        final NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
        if (navigationView == null) {
            return;
        }
        navigationView.addOnLayoutChangeListener(this);

        // Handle click on header
        final View hView = navigationView.getHeaderView(0);
        hView.setOnClickListener(this);

        // set Devices menu state
        final Menu menu = navigationView.getMenu();
        final MenuItem deviceItem = menu.findItem(R.id.nav_devices);
        deviceItem.setEnabled(Prefs.isGcmRegistered());

        // set users avatar
        final String photoUri = Prefs.getPersonPhoto();
        final ImageView photo = (ImageView) hView.findViewById(R.id.personPhoto);
        if (!photoUri.isEmpty()) {
            photo.setImageAlpha(255);
            new LoadProfileImage(photo).execute(photoUri);
        } else {
            photo.setImageResource(R.drawable.ic_account_circle_24dp);
            photo.setImageAlpha(75);
        }

        // set users Google+ cover photo
        final String coverPhotoUri = Prefs.getPersonCoverPhoto();
        final LinearLayout coverPhoto = (LinearLayout) hView.findViewById(R.id.navHeader);
        if (!coverPhotoUri.isEmpty()) {
            new LoadCoverImage(coverPhoto).execute(coverPhotoUri);
        } else {
            final Drawable drawable = ContextCompat.getDrawable(this, R.drawable.side_nav_bar);
            coverPhoto.setBackground(drawable);
        }

        // set users account information
        final TextView name = (TextView) hView.findViewById(R.id.personName);
        final TextView email = (TextView) hView.findViewById(R.id.personEmail);
        name.setText(Prefs.getPersonName());
        email.setText(Prefs.getPersonEmail());
    }

    /**
     * Initialize the main {@link RecyclerView} and connect it to its {@link ClipCursorAdapter}
     */
    private void setupRecyclerView() {
        final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.clipList);

        // required for animations to work?
        recyclerView.setHasFixedSize(true);

        // Create an empty adapter we will use to display the loaded data.
        // We pass null for the cursor, then update it in onLoadFinished()
        // Need to pass Activity for context for all UI stuff to work
        mAdapter = new ClipCursorAdapter(this);
        recyclerView.setAdapter(mAdapter);

        // handle touch events on the RecyclerView
        final ItemTouchHelper.Callback callback = new ClipItemTouchHelper(this);
        final ItemTouchHelper helper = new ItemTouchHelper(callback);
        helper.attachToRecyclerView(recyclerView);
    }

    /**
     * Initialize the {@link android.support.v7.view.menu.ActionMenuItemView} for search
     */
    private void setupSearch() {
        final SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
        final MenuItem searchItem = mMenu.findItem(R.id.action_search);
        final SearchView searchView = (SearchView) searchItem.getActionView();
        searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
        searchView.setOnQueryTextListener(this);

        if (!TextUtils.isEmpty(mQueryString)) {
            // http://stackoverflow.com/a/32397014/4468645
            searchView.post(new Runnable() {
                @Override
                public void run() {
                    MenuItemCompat.expandActionView(searchItem);
                    searchView.setQuery(mQueryString, true);
                }
            });
        }
    }

    private void setTitle(ClipItem clipItem) {
        String title = getString(R.string.app_name);
        if (AppUtils.isDualPane()) {
            if ((clipItem != null) && !TextUtils.isEmpty(clipItem.getDevice())) {
                title = title + " - " + getString(R.string.from_source) + clipItem.getDevice();
            }
        }
        setTitle(title);
    }

    private ClipViewerFragment getClipViewerFragment() {
        return (ClipViewerFragment) getSupportFragmentManager().findFragmentById(R.id.clip_viewer_container);
    }

    private void setPrefFavFilter(boolean prefFavFilter, boolean restart) {
        mFavFilter = prefFavFilter;

        if (mMenu != null) {
            final MenuItem item = mMenu.findItem(R.id.action_fav_filter);
            item.setIcon(mFavFilter ? R.drawable.ic_favorite_24dp : R.drawable.ic_favorite_outline_24dp);
            item.setTitle(mFavFilter ? R.string.action_show_all : R.string.action_show_favs);
            final int color = ContextCompat.getColor(this, R.color.icons);
            final int alpha = 255;
            MenuTint.colorMenuItem(item, color, alpha);
        }

        if (restart) {
            getSupportLoaderManager().restartLoader(0, null, this);
        }
    }

    private void setQueryString(String queryString) {
        if (!Collator.getInstance().equals(mQueryString, queryString)) {
            mQueryString = queryString;
            getSupportLoaderManager().restartLoader(0, null, this);
        }
    }

    /**
     * Background Async task to load user profile picture from url
     */
    private class LoadProfileImage extends AsyncTask<String, Void, Bitmap> {
        private final ImageView mImageView;

        LoadProfileImage(ImageView view) {
            mImageView = view;
        }

        @SuppressWarnings("resource")
        @Override
        protected Bitmap doInBackground(String... params) {
            final String urlName = params[0];
            final URL url;
            try {
                url = new URL(urlName);
            } catch (final MalformedURLException e) {
                Log.e(TAG, "Bad person bitmap URL: " + e.getMessage());
                return null;
            }
            InputStream inputStream = null;
            Bitmap bitmap = null;
            try {
                inputStream = url.openStream();
                bitmap = BitmapFactory.decodeStream(inputStream);
            } catch (final IOException e) {
                Log.e(TAG, "Failed to get person bitmap: " + e.getMessage());
            } finally {
                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                } catch (final IOException ignored) {
                    // ignore
                }
            }
            return bitmap;
        }

        @Override
        protected void onPostExecute(Bitmap result) {
            if (result != null) {
                final RoundedBitmapDrawable bg = RoundedBitmapDrawableFactory.create(getResources(), result);
                bg.setCircular(true);
                mImageView.setImageDrawable(bg);
            }
        }
    }

    /**
     * Background Async task to load user cover photo from url
     */
    private class LoadCoverImage extends AsyncTask<String, Void, Bitmap> {
        private final LinearLayout mHeader;

        LoadCoverImage(LinearLayout view) {
            mHeader = view;
        }

        @SuppressWarnings("resource")
        @Override
        protected Bitmap doInBackground(String... params) {
            final String urlName = params[0];
            final URL url;
            try {
                url = new URL(urlName);
            } catch (final MalformedURLException e) {
                Log.e(TAG, "Bad bitmap URL: " + e.getMessage());
                return null;
            }
            InputStream inputStream = null;
            Bitmap bitmap = null;
            try {
                inputStream = url.openStream();
                bitmap = BitmapFactory.decodeStream(inputStream);
            } catch (final IOException e) {
                Log.e(TAG, "Failed to get cover bitmap: " + e.getMessage());
            } finally {
                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                } catch (final IOException ignore) {
                    // ignore
                }
            }
            return bitmap;
        }

        @Override
        protected void onPostExecute(Bitmap result) {
            if (result != null) {
                final Drawable bg = new BitmapDrawable(getResources(), result);
                final int color = ContextCompat.getColor(MainActivity.this, R.color.darkener);
                bg.setColorFilter(color, PorterDuff.Mode.DARKEN);
                mHeader.setBackground(bg);
            }
        }
    }
}