com.ubuntuone.android.files.activity.FilesActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.ubuntuone.android.files.activity.FilesActivity.java

Source

/*
 * Ubuntu One Files - access Ubuntu One cloud storage on Android platform.
 * 
 * Copyright 2011-2012 Canonical Ltd.
 *   
 * This file is part of Ubuntu One Files.
 *  
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) 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.ubuntuone.android.files.activity;

import greendroid.app.GDListActivity;
import greendroid.widget.ActionBarItem;
import greendroid.widget.ActionBarItem.Type;
import greendroid.widget.LoaderActionBarItem;
import greendroid.widget.NormalActionBarItem;
import greendroid.widget.QuickActionGrid;
import greendroid.widget.QuickActionWidget;
import greendroid.widget.QuickActionWidget.OnQuickActionClickListener;

import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.WeakHashMap;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.CursorAdapter;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.apps.analytics.GoogleAnalyticsTracker;
import com.ubuntuone.android.files.Analytics;
import com.ubuntuone.android.files.Preferences;
import com.ubuntuone.android.files.R;
import com.ubuntuone.android.files.holder.FileViewHolder;
import com.ubuntuone.android.files.provider.MetaContract.Nodes;
import com.ubuntuone.android.files.provider.MetaContract.ResourceState;
import com.ubuntuone.android.files.provider.MetaUtilities;
import com.ubuntuone.android.files.provider.TransfersContract.Downloads;
import com.ubuntuone.android.files.service.MetaService;
import com.ubuntuone.android.files.service.MetaService.Status;
import com.ubuntuone.android.files.service.MetaServiceHelper;
import com.ubuntuone.android.files.service.UpDownService;
import com.ubuntuone.android.files.service.UpDownService.OnDownloadListener;
import com.ubuntuone.android.files.service.UpDownServiceHelper;
import com.ubuntuone.android.files.util.BitmapUtilities;
import com.ubuntuone.android.files.util.ChangeLogUtils;
import com.ubuntuone.android.files.util.DateUtilities;
import com.ubuntuone.android.files.util.DetachableResultReceiver;
import com.ubuntuone.android.files.util.DetachableResultReceiver.Receiver;
import com.ubuntuone.android.files.util.FileUtilities;
import com.ubuntuone.android.files.util.Log;
import com.ubuntuone.android.files.util.NetworkUtil;
import com.ubuntuone.android.files.util.PathTracker;
import com.ubuntuone.android.files.util.TransferUtils;
import com.ubuntuone.android.files.util.U1CroppedImageDownloader;
import com.ubuntuone.android.files.util.U1ImageDownloader;
import com.ubuntuone.android.files.util.UIUtil;
import com.ubuntuone.android.files.util.UIUtil.BlackQuickAction;
import com.ubuntuone.android.files.util.VersionUtilities;
import com.ubuntuone.api.files.model.U1Node;
import com.ubuntuone.api.files.model.U1NodeKind;
import com.ubuntuone.api.files.util.U1Failure;

@SuppressWarnings("deprecation") // TODO Update GA tracker calls.
public class FilesActivity extends GDListActivity implements Receiver, OnDownloadListener {
    private static final String TAG = FilesActivity.class.getSimpleName();

    private static interface ActionBar {
        public static final int REFRESH = 0;
        public static final int UPLOAD = 1;
    }

    private static interface QuickAction {
        public static final int UPLOAD_IMAGE = 0;
        public static final int UPLOAD_VIDEO = 1;
        public static final int UPLOAD_AUDIO = 2;
        public static final int UPLOAD_FILE = 3;
        public static final int NEW_FOLDER = 4;
    }

    private static final int REQUEST_SIGN_IN = 1;
    private static final int REQUEST_PREFERENCES = 2;
    private static final int REQUEST_ADD_FILE = 3;
    private static final int REQUEST_PREVIEW = 4;

    public static final String ACTION_PICK_AUTO_UPLOAD_DIRECTORY = "com.ubuntuone.android.files.ACTION_PICK_AUTO_UPLOAD_DIRECTORY";

    private final String EXTRA_DIALOG_EXTRAS = "extra_dialog_extras";
    private final String EXTRA_TOP_POSITION = "extra_top_position";
    private final String EXTRA_TOP = "extra_top";

    private final int DIALOG_DOWNLOAD_ID = 1;
    private final int DIALOG_RENAME_ID = 2;
    private final int DIALOG_DELETE_ID = 3;
    private final int DIALOG_EMPTY_DIRECTORY_ID = 4;
    private final int DIALOG_NEW_DIRECTORY_ID = 5;
    private final int DIALOG_CHECK_DIRECTORY_SIZE_ID = 7;
    private final int DIALOG_PICK_UPLOAD_DIRECTORY_ID = 8;
    private final int DIALOG_NO_NETWORK = 9;
    private final int DIALOG_CREATE_PUBLIC_LINK_ID = 10;

    private Dialog mRenameDialog;
    private Dialog mNewDirectoryDialog;
    private Dialog mCheckDirectorySizeDialog;
    private Dialog mCreatePublicLinkDialog;
    private Bundle mDialogExtras;

    private GoogleAnalyticsTracker mTracker;

    private Handler mHandler;
    private DetachableResultReceiver mReceiver;
    private SignOutBroadcastReceiver mSignOutReceiver;

    private ContentResolver mResolver;

    private LoaderActionBarItem mLoaderItem;
    private NormalActionBarItem mUploadItem;
    private QuickActionWidget mUploadGrid;

    private FileViewHolder mContextViewHolder;

    private TextView mEmptyTextView;
    private int mTopPosition, mTop;

    private FilesAdapter mAdapter;
    private WeakHashMap<String, FileViewHolder> holders = new WeakHashMap<String, FileViewHolder>();
    private U1ImageDownloader mImageDownloader;
    private PathTracker mPathTracker;
    private String currentVolumeResourcePath;
    private boolean isGettingVolume = false;

    private boolean mIsPickAutoUploadDirectoryMode = false;
    private boolean mIsPickShareWithDirectoryMode = false;
    private Intent shareIntent = null;

    @Override
    public int createLayout() {
        return R.layout.activity_list;
    }

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

        mTracker = GoogleAnalyticsTracker.getInstance();
        mTracker.start(Analytics.U1F_ACCOUNT, this);
        mTracker.trackPageView(TAG);

        mHandler = new Handler();
        mReceiver = new DetachableResultReceiver(mHandler);

        mSignOutReceiver = new SignOutBroadcastReceiver();
        IntentFilter intentFilter = new IntentFilter("com.ubuntuone.android.files.SIGN_OUT");
        LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
        lbm.registerReceiver(mSignOutReceiver, intentFilter);

        mResolver = getContentResolver();

        // Override splash screen background.
        getWindow().setBackgroundDrawableResource(android.R.drawable.screen_background_light);

        final ImageButton homeButton = (ImageButton) getActionBar().findViewById(R.id.gd_action_bar_home_item);
        homeButton.setImageResource(R.drawable.u1_logo);

        // Refresh
        addActionBarItem(Type.Refresh);
        mLoaderItem = (LoaderActionBarItem) getActionBar().getItem(ActionBar.REFRESH);
        mLoaderItem.setDrawable(R.drawable.ic_act_action_bar_refresh);
        // Upload
        addActionBarItem(Type.Add);
        mUploadItem = (NormalActionBarItem) getActionBar().getItem(ActionBar.UPLOAD);
        mUploadItem.setDrawable(R.drawable.ic_act_action_bar_add);

        onCreateUploadQuickActionGrid();

        getActionBar().setBackgroundResource(R.drawable.action_bar_background);

        mEmptyTextView = (TextView) findViewById(android.R.id.empty);

        ListView listView = getListView();
        listView.setEmptyView(mEmptyTextView);
        listView.setFastScrollEnabled(true);

        registerForContextMenu(getListView());

        mPathTracker = new PathTracker();

        if (NetworkUtil.getAvailableNetworkType(this) == -1) {
            UIUtil.showToast(this, R.string.toast_working_offline, true);
        }

        if (Preferences.hasTokens(this)) {
            mImageDownloader = U1CroppedImageDownloader.SMALL;
            onRefresh(null);
            suggestAutoUploadConfiguration();
            ChangeLogUtils.maybeShowChangelog(this);
        } else {
            final Intent intent = getIntent();
            final String data = (intent != null) ? intent.getDataString() : null;
            if (data != null && data.startsWith("x-ubuntuone-sso")) {
                validate();
            } else {
                signIn();
            }
        }

        String action = getIntent() != null ? getIntent().getAction() : null;
        if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
            mIsPickShareWithDirectoryMode = true;
            shareIntent = getIntent();
            showDialog(DIALOG_PICK_UPLOAD_DIRECTORY_ID);
        }
    }

    /*
     * Sadly, FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET works only for the launcher.
     * If home button long-pressed and used to get back to activity, a validation
     * request leftover may be visible. I have no fix for this edge case ATM. 
     */

    private void signIn() {
        final Intent intent = new Intent(LoginActivity.ACTION_SIGN_IN);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
        startActivityForResult(intent, REQUEST_SIGN_IN);
    }

    private void validate() {
        final Intent intent = new Intent(LoginActivity.ACTION_VALIDATE);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
        startActivityForResult(intent, REQUEST_SIGN_IN);
    }

    private void suggestAutoUploadConfiguration() {
        if (!Preferences.isPhotoUploadConfigured()) {
            startActivity(new Intent(this, AutoUploadSetupActivity.class));
        }
    }

    private void onCreateUploadQuickActionGrid() {
        mUploadGrid = new QuickActionGrid(this);
        mUploadGrid.addQuickAction(
                new BlackQuickAction(this, R.drawable.ic_act_upload_photo, R.string.action_bar_add_image));
        mUploadGrid.addQuickAction(
                new BlackQuickAction(this, R.drawable.ic_act_upload_video, R.string.action_bar_add_video));
        mUploadGrid.addQuickAction(
                new BlackQuickAction(this, R.drawable.ic_act_upload_audio, R.string.action_bar_add_audio));
        mUploadGrid.addQuickAction(
                new BlackQuickAction(this, R.drawable.ic_act_upload_file, R.string.action_bar_add_file));
        mUploadGrid.addQuickAction(
                new BlackQuickAction(this, R.drawable.ic_act_new_folder, R.string.action_bar_new_directory));
        mUploadGrid.setOnQuickActionClickListener(mUploadQuickActionListener);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (mUploadGrid != null && mUploadGrid.isShowing()) {
            mUploadGrid.dismiss();
        }
        onCreateUploadQuickActionGrid();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // Save dialog extras.
        outState.putBundle(EXTRA_DIALOG_EXTRAS, mDialogExtras);
        // Save list position.
        final ListView l = getListView();
        int topPosition = l.getFirstVisiblePosition();
        final View topView = l.getChildAt(0);
        final int top = (topView == null) ? 0 : topView.getTop();
        outState.putInt(EXTRA_TOP_POSITION, topPosition);
        outState.putInt(EXTRA_TOP, top);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        // Restore dialog extras.
        mDialogExtras = savedInstanceState.getBundle(EXTRA_DIALOG_EXTRAS);
        // Restore list view position.
        int topPosition = savedInstanceState.getInt(EXTRA_TOP_POSITION);
        int top = savedInstanceState.getInt(EXTRA_TOP);
        getListView().setSelectionFromTop(topPosition, top);

        super.onRestoreInstanceState(savedInstanceState);
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        String data = intent.getDataString();
        String action = intent.getAction();
        if (data != null && data.startsWith("x-ubuntuone-sso")) {
            validate();
        } else if (ACTION_PICK_AUTO_UPLOAD_DIRECTORY.equals(action)) {
            mIsPickAutoUploadDirectoryMode = true;
            showDialog(DIALOG_PICK_UPLOAD_DIRECTORY_ID);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mReceiver != null) {
            mReceiver.setReceiver(this);
            if (MetaService.isSyncRunning()) {
                awaitWithListEmptyTextView();
                showSpinner();
            } else {
                hideSpinner();
            }
        }
        setCursorAdapterInBackground();
    }

    /**
     * Requests appropriate cursor and sets the {@link FilesAdapter} on
     * a background thread, to avoid ANR due to database operation. Also,
     * we're not using deprecated .requery() anymore.
     */
    private void setCursorAdapterInBackground() {
        mHandler.post(new Runnable() {
            public void run() {
                final Cursor cursor = mPathTracker.isAtRoot() ? MetaUtilities.getVisibleTopNodesCursor()
                        : getFilesCurosor(mPathTracker.getCurrentNode());
                startManagingCursor(cursor);
                // We have a new cursor, set a new list adapter.
                runOnUiThread(new Runnable() {
                    public void run() {
                        if (mAdapter == null) {
                            mAdapter = new FilesAdapter(FilesActivity.this, cursor, true);
                            setListAdapter(mAdapter);
                        } else {
                            mAdapter.changeCursor(cursor);
                            mAdapter.notifyDataSetChanged();
                        }
                    }
                });
            }
        });
    }

    @Override
    public void startManagingCursor(Cursor c) {
        if (Build.VERSION.SDK_INT < VersionUtilities.HONEYCOMB) {
            super.startManagingCursor(c);
        }
    }

    @Override
    protected void onPause() {
        if (mReceiver != null) {
            mReceiver.detach();
        }
        super.onPause();
    }

    @Override
    public void onDestroy() {
        LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
        lbm.unregisterReceiver(mSignOutReceiver);

        if (mTracker != null) {
            mTracker.dispatch();
            mTracker.stop();
        }
        super.onDestroy();
    }

    @Override
    protected Dialog onCreateDialog(int id) {
        Dialog dialog;
        switch (id) {
        case DIALOG_DOWNLOAD_ID:
            dialog = buildDownloadDialog(mDialogExtras);
            break;
        case DIALOG_RENAME_ID:
            dialog = mRenameDialog = buildRenameDialog(mDialogExtras);
            break;
        case DIALOG_DELETE_ID:
            dialog = buildDeleteDialog(mDialogExtras);
            break;
        case DIALOG_EMPTY_DIRECTORY_ID:
            dialog = buildEmptyDirDialog(mDialogExtras);
            break;
        case DIALOG_NEW_DIRECTORY_ID:
            dialog = mNewDirectoryDialog = buildNewDirDialog(mDialogExtras);
            break;
        case DIALOG_CHECK_DIRECTORY_SIZE_ID:
            dialog = mCheckDirectorySizeDialog = buildCheckDirectorySizeDialog(mDialogExtras);
            break;
        case DIALOG_PICK_UPLOAD_DIRECTORY_ID:
            dialog = buildPickUploadDirectory(mDialogExtras);
            break;
        case DIALOG_NO_NETWORK:
            dialog = buildNoNetworkDialog();
            break;
        case DIALOG_CREATE_PUBLIC_LINK_ID:
            dialog = mCreatePublicLinkDialog = buildCreatePublicLinkDialog();
            break;
        default:
            dialog = null;
            break;
        }
        return dialog;
    }

    private OnClickListener onClickDismiss = new OnClickListener() {

        public void onClick(DialogInterface dialog, int which) {
            dialog.dismiss();
        }

    };

    private Dialog buildNoNetworkDialog() {
        final AlertDialog dialog = new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert)
                .setTitle(R.string.dialog_no_network_title).setMessage(R.string.dialog_no_network_message)
                .setPositiveButton(R.string.ok, onClickDismiss).create();
        return dialog;
    }

    private Dialog buildInsufficientSpaceDialog(long needed, long free) {
        final String message = String.format(getString(R.string.dialog_no_space_message_fmt),
                FileUtilities.getHumanReadableSize(needed), FileUtilities.getHumanReadableSize(free));
        final AlertDialog dialog = new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert)
                .setTitle(R.string.dialog_no_space_title).setMessage(message)
                .setPositiveButton(R.string.ok, new OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                        removeDialog(DIALOG_DOWNLOAD_ID);
                    }
                }).create();
        return dialog;
    }

    private Dialog buildEmptyDirDialog(final Bundle extras) {
        final String title = extras.getString(MetaService.EXTRA_NAME);
        final AlertDialog dialog = new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_info)
                .setTitle(title).setMessage(R.string.dialog_nothing_to_download)
                .setPositiveButton(R.string.ok, new OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                        removeDialog(DIALOG_EMPTY_DIRECTORY_ID);
                    }
                }).create();
        return dialog;
    }

    private Dialog buildCheckDirectorySizeDialog(final Bundle extras) {
        final ProgressDialog dialog = new ProgressDialog(this);
        dialog.setMessage(getString(R.string.dialog_checking_size_message));
        dialog.setIndeterminate(true);
        dialog.setCancelable(true);
        return dialog;
    }

    /**
     * This dialog applies to downloading directories only.
     * 
     * @param extras
     * @return
     */
    private Dialog buildDownloadDialog(final Bundle extras) {
        final String resourcePath = extras.getString(MetaService.EXTRA_RESOURCE_PATH);
        final long size = extras.getLong(MetaService.EXTRA_SIZE);
        final String sizeText = FileUtilities.getHumanReadableSize(size);

        final long free = StorageActivity.getAvailableExternalStorageSize();
        if (free < size) {
            return buildInsufficientSpaceDialog(size, free);
        }

        final StringBuilder msgBuilder = new StringBuilder(32);
        msgBuilder.append(String.format(getString(R.string.dialog_downloading_dir_is_x_large), sizeText));
        int network = NetworkUtil.getAvailableNetworkType(this);
        if (network == -1) {
            return buildNoNetworkDialog();
        } else if (network == ConnectivityManager.TYPE_MOBILE) {
            msgBuilder.append(" ");
            msgBuilder.append(getString(R.string.dialog_downloading_dir_on_mobile));
        }
        msgBuilder.append(" ");
        msgBuilder.append(getString(R.string.dialog_downloading_dir_are_you_sure));

        final OnClickListener onClick = new OnClickListener() {

            private void download() {
                UpDownServiceHelper.download(FilesActivity.this, resourcePath);
            }

            public void onClick(DialogInterface dialog, int which) {
                switch (which) {
                case Dialog.BUTTON_POSITIVE:
                    download();
                    break;
                case Dialog.BUTTON_NEGATIVE:
                    // Will dismiss below.
                    break;
                default:
                    Log.e(TAG, "no such button");
                    break;
                }
                removeDialog(DIALOG_DOWNLOAD_ID);
            }

        };

        final AlertDialog dialog = new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_info)
                .setTitle(R.string.dialog_downloading_dir_title).setMessage(msgBuilder.toString())
                .setPositiveButton(R.string.yes, onClick).setNegativeButton(R.string.no, onClick).create();
        return dialog;
    }

    private Dialog buildCreatePublicLinkDialog() {
        final ProgressDialog dialog = new ProgressDialog(this);
        dialog.setMessage(getString(R.string.dialog_creating_link));
        dialog.setIndeterminate(true);
        dialog.setCancelable(false);
        return dialog;
    }

    private Dialog buildRenameDialog(final Bundle extras) {
        final String resourcePath = extras.getString(MetaService.EXTRA_RESOURCE_PATH);
        final String name = extras.getString(MetaService.EXTRA_NAME);
        final String path = extras.getString(MetaService.EXTRA_PATH);
        Log.d(TAG, "dialog built with PATH: " + path);
        Log.d(TAG, "buildRenameDialog: name is " + name);

        final OnClickListener onClick = new OnClickListener() {

            private final String reserved = "/";

            private void rename(DialogInterface dialog) {
                EditText newNameEdit = (EditText) mRenameDialog.findViewById(R.id.dialog_edit_text);
                final String newName = newNameEdit.getText().toString();
                if (TextUtils.isEmpty(newName)) {
                    UIUtil.showToast(FilesActivity.this, R.string.toast_cant_rename_to_nothing);
                    dialog.dismiss();
                    return;
                } else if (newName.equals(name)) {
                    UIUtil.showToast(FilesActivity.this, R.string.toast_no_change);
                    return;
                }

                for (int i = 0; i < reserved.length(); i++) {
                    if (newName.indexOf(reserved.charAt(i)) > -1) {
                        final String msg = String.format(getString(R.string.toast_file_name_cant_contain),
                                reserved.charAt(i));

                        UIUtil.showToast(FilesActivity.this, msg, true);
                        return;
                    }
                }

                final int cutAt = path.lastIndexOf("/") + 1;
                final String parent = path.substring(0, cutAt);
                final String newPath = parent.concat(newName);
                MetaServiceHelper.rename(FilesActivity.this, resourcePath, newPath, newName, mReceiver);
            }

            public void onClick(DialogInterface dialog, int which) {

                switch (which) {
                case Dialog.BUTTON_POSITIVE:
                    rename(dialog);
                    break;
                case Dialog.BUTTON_NEGATIVE:
                    // Will dismiss below.
                    break;
                default:
                    Log.e(TAG, "no such button");
                    break;
                }
                removeDialog(DIALOG_RENAME_ID);
            }

        };

        // TODO karni: Make this a regular text dialog.
        final View dialogView = getLayoutInflater().inflate(R.layout.dialog_edittext,
                (ViewGroup) findViewById(R.id.dialog_root));
        final EditText editText = (EditText) dialogView.findViewById(R.id.dialog_edit_text);
        editText.setText(name);

        final AlertDialog dialog = new AlertDialog.Builder(this).setView(dialogView)
                .setIcon(android.R.drawable.ic_dialog_info).setTitle(R.string.dialog_rename_directory_title)
                .setPositiveButton(R.string.context_rename, onClick).setNegativeButton(R.string.cancel, onClick)
                .create();
        return dialog;
    }

    private Dialog buildDeleteDialog(final Bundle extras) {
        final String resourcePath = extras.getString(MetaService.EXTRA_RESOURCE_PATH);
        final String name = extras.getString(MetaService.EXTRA_NAME);

        final OnClickListener onClick = new OnClickListener() {

            public void onClick(DialogInterface dialog, int which) {
                switch (which) {
                case Dialog.BUTTON_POSITIVE:
                    MetaServiceHelper.deleteNode(FilesActivity.this, resourcePath, mReceiver);
                    break;
                case Dialog.BUTTON_NEGATIVE:
                    // Will dismiss below.
                    break;
                default:
                    Log.e(TAG, "no such button");
                    break;
                }
                removeDialog(DIALOG_DELETE_ID);
            }

        };

        final AlertDialog dialog = new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert)
                .setTitle(name).setMessage(R.string.dialog_delete_item_message)
                .setPositiveButton(R.string.yes, onClick).setNegativeButton(R.string.no, onClick).create();
        return dialog;
    }

    private Dialog buildNewDirDialog(final Bundle extras) {
        final String path = mPathTracker.isAtRoot() ? Preferences.U1_RESOURCE : mPathTracker.getCurrentNode();
        Log.d(TAG, "dialog built with path: " + path);

        final OnClickListener onClick = new OnClickListener() {

            private final String reserved = "|\\?*<\":>+[]/'";

            private void create(DialogInterface dialog) {
                EditText editText = (EditText) mNewDirectoryDialog.findViewById(R.id.dialog_edit_text);
                final String newName = editText.getText().toString();
                if (newName.length() == 0) {
                    UIUtil.showToast(FilesActivity.this, R.string.toast_name_cant_be_nothing);
                    dialog.dismiss();
                    return;
                }

                for (int i = 0; i < reserved.length(); i++) {
                    if (newName.indexOf(reserved.charAt(i)) > -1) {
                        final String msg = String.format(getString(R.string.toast_file_name_cant_contain),
                                reserved.charAt(i));

                        UIUtil.showToast(FilesActivity.this, msg, true);
                        return;
                    }
                }

                final String resourcePath = path + "/" + newName;
                MetaServiceHelper.createDirectory(FilesActivity.this, resourcePath, newName, mReceiver);
            }

            public void onClick(DialogInterface dialog, int which) {

                switch (which) {
                case Dialog.BUTTON_POSITIVE:
                    create(dialog);
                    break;
                case Dialog.BUTTON_NEGATIVE:
                    // Will dismiss below.
                    break;
                default:
                    Log.e(TAG, "no such button");
                    break;
                }
                removeDialog(DIALOG_NEW_DIRECTORY_ID);
            }

        };

        final View dialogView = getLayoutInflater().inflate(R.layout.dialog_edittext,
                (ViewGroup) findViewById(R.id.dialog_root));
        final TextView textView = (TextView) dialogView.findViewById(R.id.dialog_text);
        textView.setText(R.string.dialog_create_directory_title);

        final AlertDialog dialog = new AlertDialog.Builder(this).setView(dialogView)
                .setIcon(android.R.drawable.ic_dialog_info).setTitle(R.string.dialog_create_directory_title)
                .setPositiveButton(R.string.create, onClick).setNegativeButton(R.string.cancel, onClick).create();
        return dialog;
    }

    private Dialog buildPickUploadDirectory(final Bundle extras) {
        final OnClickListener onClick = new OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        };

        final AlertDialog dialog = new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_info)
                .setTitle(R.string.dialog_pick_upload_directory_title)
                .setMessage(getText(R.string.dialog_pick_upload_directory_message))
                .setPositiveButton(R.string.ok, onClick).create();
        return dialog;
    }

    @Override
    public boolean onHandleActionBarItemClick(ActionBarItem item, int position) {
        switch (position) {
        case ActionBar.REFRESH:
            onActionBarRereshClicked();
            break;
        case ActionBar.UPLOAD:
            if (mPathTracker.isAtRoot()) {
                Toast.makeText(this, "Please select a folder first.", Toast.LENGTH_SHORT).show();
            } else {
                onActionBarUploadClicked(item.getItemView());
            }
            break;
        default:
            Log.e(TAG, "unknown action bar action");
            return false;
        }
        return true;
    }

    private void onRefresh(String resourcePath) {
        Log.i(TAG, "onRefresh: " + ((resourcePath == null) ? "root" : resourcePath));
        if (resourcePath == null) {
            // Refresh user info and volumes (with deltas)
            MetaServiceHelper.getUserInfo(this, mReceiver);

            // Refresh ~/Ubuntu One (first time only)
            resourcePath = Preferences.U1_RESOURCE;
            if (!MetaUtilities.isCached(resourcePath)) {
                Log.i(TAG, "Refreshing ~/Ubuntu One");
                MetaServiceHelper.getNode(this, resourcePath, mReceiver);
            }

            // Refresh photos UDF/folder (first time only)
            resourcePath = Preferences.getPhotoUploadResourcePath();
            if (!MetaUtilities.isCached(resourcePath) && Preferences.isPhotoUploadEnabled()) {
                Log.i(TAG, "Refreshing photo auto upload location");
                MetaServiceHelper.getNode(this, resourcePath, mReceiver);
            }
        } else {
            if (!MetaUtilities.isCached(resourcePath)) {
                MetaServiceHelper.getNode(this, resourcePath, mReceiver);
            } else {
                if (!isGettingVolume) {
                    isGettingVolume = true;
                    MetaServiceHelper.getVolume(this, currentVolumeResourcePath, mReceiver);
                }
            }
        }
    }

    private void onActionBarRereshClicked() {
        if (!NetworkUtil.isConnected(this)) {
            hideSpinner();
            showDialog(DIALOG_NO_NETWORK);
        } else if (mPathTracker.isAtRoot()) {
            onRefresh(null);
        } else {
            String resourcePath = mPathTracker.getCurrentNode();
            MetaUtilities.setIsCached(resourcePath, false);
            onRefresh(resourcePath);
        }
    }

    private void onActionBarUploadClicked(View v) {
        mUploadGrid.show(v);
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        menu.clear();
        if (mIsPickAutoUploadDirectoryMode || mIsPickShareWithDirectoryMode) {
            if (!mPathTracker.isAtRoot()) {
                inflater.inflate(R.menu.options_menu_pick_auto_upload, menu);
            } else {
                Toast.makeText(this, "Select a folder first.", Toast.LENGTH_SHORT).show();
            }
        } else {
            inflater.inflate(R.menu.options_menu_dashboard, menu);
        }
        return super.onPrepareOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.option_refresh:
            onOptionRefreshSelected();
            break;
        case R.id.option_preferences:
            onOptionPreferencesSelected();
            break;
        case R.id.option_upload_here:
            if (mIsPickShareWithDirectoryMode) {
                onOptionShareSelected();
            } else if (mIsPickAutoUploadDirectoryMode) {
                onOptionUploadHereSelected();
            }
            break;
        case R.id.option_cancel:
            if (mIsPickShareWithDirectoryMode) {
                onOptionCancelSelected();
                finish();
            } else {
                onOptionCancelSelected();
            }
            break;
        default:
            return false;
        }
        return true;
    }

    private void onOptionRefreshSelected() {
        String resourcePath = mPathTracker.getCurrentNode();
        if (resourcePath != null) {
            MetaUtilities.setIsCached(resourcePath, false);
        }
        onRefresh(resourcePath);
    }

    private void onOptionPreferencesSelected() {
        final Intent intent = new Intent(this, PreferencesActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
        startActivityForResult(intent, REQUEST_PREFERENCES);
    }

    private void onOptionShareSelected() {
        String node = mPathTracker.getCurrentNode();
        String uploadResourcePath = (node == null) ? Preferences.U1_RESOURCE : node;

        String action = shareIntent != null ? shareIntent.getAction() : null;
        if (Intent.ACTION_SEND.equals(action)) {
            onSendAction(shareIntent, uploadResourcePath);
        } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
            onSendMultipleAction(shareIntent, uploadResourcePath);
        }
        shareIntent = null;
        mIsPickShareWithDirectoryMode = false;
        finish();
    }

    private void onSendAction(Intent intent, String resourcePath) {
        if (intent.hasExtra(Intent.EXTRA_STREAM)) {
            final Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
            UpDownServiceHelper.upload(this, uri, resourcePath, false);
        }
    }

    private void onSendMultipleAction(Intent intent, String resourcePath) {
        if (intent.hasExtra(Intent.EXTRA_STREAM)) {
            ArrayList<Parcelable> list = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
            for (Parcelable item : list) {
                Uri uri = (Uri) item;
                UpDownServiceHelper.upload(this, uri, resourcePath, false);
            }
        }
    }

    private void onOptionUploadHereSelected() {
        final String node = mPathTracker.getCurrentNode();
        String uploadDirectory = null;
        if (node == null) {
            uploadDirectory = Preferences.U1_RESOURCE.substring(3);
        } else {
            uploadDirectory = node.substring(3);
        }
        Preferences.setPhotoUploadDirectory(uploadDirectory);
        UIUtil.showToast(this, getString(R.string.toast_photos_will_auto_upload_to_fmt, uploadDirectory), true);
        mIsPickAutoUploadDirectoryMode = false;
    }

    private void onOptionCancelSelected() {
        mIsPickAutoUploadDirectoryMode = false;
        mIsPickShareWithDirectoryMode = false;
    }

    public void onReceiveResult(int resultCode, final Bundle resultData) {
        final String lastResourcePath = resultData.getString(MetaService.EXTRA_RESOURCE_PATH);

        switch (resultCode) {
        case Status.RUNNING:
            showSpinner();
            awaitWithListEmptyTextView();
            break;
        case Status.PROGRESS:
            // Unused.
            break;
        case Status.FINISHED:
            hideSpinner();
            setCursorAdapterInBackground();

            if (lastResourcePath != null && lastResourcePath.equals(currentVolumeResourcePath)) {
                resetListEmptyTextView(null);
                isGettingVolume = false;
                return;
            }

            final String currentResourcePath = mPathTracker.getCurrentNode();
            if (currentResourcePath == null) {
                // Most probably the user has no files yet. If he has,
                // he should already see them and this will have no impact.
                resetListEmptyTextView(null);
            } else if (currentResourcePath.equals(lastResourcePath)) {
                resetListEmptyTextView(lastResourcePath);
            }

            if (mCheckDirectorySizeDialog != null && mCheckDirectorySizeDialog.isShowing()) {
                final String contextResourcePath = mDialogExtras.getString(MetaService.EXTRA_RESOURCE_PATH);
                if (lastResourcePath != null && lastResourcePath.equals(contextResourcePath)) {
                    mCheckDirectorySizeDialog.dismiss();
                    removeDialog(DIALOG_CHECK_DIRECTORY_SIZE_ID);
                    // Calculate the size of FILE entries from the directory.
                    final long size = MetaUtilities.getDirectorySize(contextResourcePath, false);
                    mDialogExtras.putLong(MetaService.EXTRA_SIZE, size);
                    if (size == 0L) {
                        showDialog(DIALOG_EMPTY_DIRECTORY_ID);
                    } else {
                        showDialog(DIALOG_DOWNLOAD_ID);
                    }
                }
            }

            if (mCreatePublicLinkDialog != null && mCreatePublicLinkDialog.isShowing()) {
                mCreatePublicLinkDialog.dismiss();
                final String contextResourcePath = mDialogExtras.getString(MetaService.EXTRA_RESOURCE_PATH);
                if (lastResourcePath != null && lastResourcePath.equals(contextResourcePath)) {
                    if (MetaUtilities.getPublicUrl(contextResourcePath) != null) {
                        onFileShareLinkClicked(mContextViewHolder);
                    }
                }
            }
            break;
        case Status.ERROR:
            if (mCheckDirectorySizeDialog != null && mCheckDirectorySizeDialog.isShowing()) {
                mCheckDirectorySizeDialog.dismiss();
                removeDialog(DIALOG_CHECK_DIRECTORY_SIZE_ID);
            }

            final String errorMessage = resultData.getString(MetaService.EXTRA_ERROR);
            // XXX This does the job, but in a terrible way. Needs more love.
            if (errorMessage == null || errorMessage.equals("auth failed")
                    || errorMessage.toLowerCase().contains("unauthorized")) {
                signIn();
            } else {
                runOnUiThread(new Runnable() {
                    public void run() {
                        hideSpinner();
                        resetListEmptyTextView(lastResourcePath);
                        final String msg = String.format(getString(R.string.error_fmt), errorMessage);
                        UIUtil.showToast(FilesActivity.this, msg, true);
                    }
                });
            }

            break;
        default:
            break;
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // Was the Back button pressed?
        if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
            if (!mPathTracker.isAtRoot()) {
                mAdapter.cd(this);
                getListView().setSelectionFromTop(mTopPosition, mTop);
                mTopPosition = mTop = 0;
                return true;
            }
        }
        return super.onKeyDown(keyCode, event);
    }

    private OnQuickActionClickListener mUploadQuickActionListener = new OnQuickActionClickListener() {

        public void onQuickActionClicked(QuickActionWidget widget, int position) {
            switch (position) {
            case QuickAction.UPLOAD_IMAGE:
                onUploadImageClicked();
                break;
            case QuickAction.UPLOAD_VIDEO:
                onUploadVideoClicked();
                break;
            case QuickAction.UPLOAD_AUDIO:
                onUploadAudioClicked();
                break;
            case QuickAction.UPLOAD_FILE:
                onUploadFileClicked();
                break;
            case QuickAction.NEW_FOLDER:
                onNewFolderClicked();
                break;

            default:
                Log.e(TAG, "unknown upload quick action");
                break;
            }
        }

    };

    private void onUploadImageClicked() {
        onUpload("image/*", R.string.dialog_pick_image_title, false);
    }

    private void onUploadVideoClicked() {
        onUpload("video/*", R.string.dialog_pick_video_title, false);
    }

    private void onUploadAudioClicked() {
        onUpload("audio/*", R.string.dialog_pick_audio_title, false);
    }

    private void onUploadFileClicked() {
        onUpload("*/*", R.string.dialog_pick_file_title, true);
    }

    private void onNewFolderClicked() {
        showDialog(DIALOG_NEW_DIRECTORY_ID);
    }

    private void onUpload(final String type, final int titleResId, boolean openable) {
        final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType(type);
        if (openable) {
            intent.addCategory(Intent.CATEGORY_OPENABLE);
        }
        final Intent chooser = Intent.createChooser(intent, getString(titleResId));
        startActivityForResult(chooser, REQUEST_ADD_FILE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        switch (requestCode) {
        case REQUEST_SIGN_IN:
            if (resultCode == RESULT_OK) {
                mImageDownloader = U1CroppedImageDownloader.SMALL;
                onRefresh(null);
                suggestAutoUploadConfiguration();
                ChangeLogUtils.maybeShowChangelog(this);
            } else if (resultCode != RESULT_OK) {
                finish();
            }
            break;

        case REQUEST_PREFERENCES:
            if (resultCode == PreferencesActivity.RESULT_UNLINKED) {
                finish();
            }
            break;

        case REQUEST_ADD_FILE:
            if (resultCode == RESULT_OK && data != null) {
                final Uri uri = data.getData();
                String parentResourcePath = mPathTracker.getCurrentNode();
                if (parentResourcePath == null) {
                    parentResourcePath = Preferences.U1_RESOURCE;
                }
                Log.d(TAG, "will upload " + uri + " to " + parentResourcePath);
                UpDownServiceHelper.upload(this, uri, parentResourcePath, false);
            } else {
                Log.i(TAG, "no file selected");
                UIUtil.showToast(this, R.string.files_activity_no_files);
            }
            break;
        }
    }

    private void onDirectoryDownloadClicked(final FileViewHolder holder) {
        final Bundle extras = mDialogExtras = new Bundle();
        extras.putString(MetaService.EXTRA_RESOURCE_PATH, holder.resourcePath);
        extras.putString(MetaService.EXTRA_NAME, holder.filename);
        // TODO karni: make recursive download an option
        showDialog(DIALOG_CHECK_DIRECTORY_SIZE_ID);
        MetaServiceHelper.getNode(this, holder.resourcePath, mReceiver);
    }

    private void onDirectoryRenameClicked(final FileViewHolder holder) {
        final String path = MetaUtilities.getStringField(holder.resourcePath, Nodes.NODE_PATH);
        final Bundle extras = mDialogExtras = new Bundle();
        extras.putString(Nodes.NODE_RESOURCE_PATH, holder.resourcePath);
        extras.putString(Nodes.NODE_PATH, path);
        extras.putString(Nodes.NODE_NAME, holder.filename);
        showDialog(DIALOG_RENAME_ID);
    }

    private void onDirectoryDeleteClicked(final FileViewHolder holder) {
        final Bundle extras = mDialogExtras = new Bundle();
        extras.putString(MetaService.EXTRA_RESOURCE_PATH, holder.resourcePath);
        extras.putString(MetaService.EXTRA_NAME, holder.filename);
        showDialog(DIALOG_DELETE_ID);
    }

    private void onFileDownloadClicked(FileViewHolder holder) {
        downloadFile(holder.resourcePath);
    }

    private void onFileCancelDownloadClicked(FileViewHolder holder) {
        Intent intent = new Intent(UpDownService.ACTION_CANCEL_DOWNLOAD);
        Uri uri = TransferUtils.getDownloadUriByResourcePath(getContentResolver(), holder.resourcePath);
        if (uri != null) {
            intent.setData(uri);
            LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
        } else {
            Log.w(TAG, "onFileCancelDownloadClicked() download uri not found");
        }
    }

    private void onFileRenameClicked(final FileViewHolder holder) {
        final String path = MetaUtilities.getStringField(holder.resourcePath, Nodes.NODE_PATH);
        final Bundle extras = mDialogExtras = new Bundle();
        extras.putString(Nodes.NODE_RESOURCE_PATH, holder.resourcePath);
        extras.putString(Nodes.NODE_PATH, path);
        extras.putString(Nodes.NODE_NAME, holder.filename);
        showDialog(DIALOG_RENAME_ID);
    }

    private void onFileDeleteClicked(final FileViewHolder holder) {
        final Bundle extras = mDialogExtras = new Bundle();
        extras.putString(MetaService.EXTRA_RESOURCE_PATH, holder.resourcePath);
        extras.putString(MetaService.EXTRA_NAME, holder.filename);
        showDialog(DIALOG_DELETE_ID);
    }

    private void onFileDeleteFromDeviceClicked(final FileViewHolder holder) {
        String path = FileUtilities.getFilePathFromResourcePath(holder.resourcePath);
        FileUtilities.removeSilently(path);
        MetaUtilities.setStateAndData(holder.resourcePath, null, null);
        MetaUtilities.notifyChange(Nodes.CONTENT_URI);
    }

    private void onFileCreateLinkClicked(final FileViewHolder holder) {
        final Bundle extras = mDialogExtras = new Bundle();
        extras.putString(Nodes.NODE_RESOURCE_PATH, holder.resourcePath);

        if (MetaUtilities.getPublicUrl(holder.resourcePath) == null) {
            showDialog(DIALOG_CREATE_PUBLIC_LINK_ID);
            MetaServiceHelper.changePublicAccess(this, holder.resourcePath, true, mReceiver);
        } else {
            onFileShareLinkClicked(holder);
        }
    }

    private void onFileDisableLinkClicked(final FileViewHolder holder) {
        final Bundle extras = mDialogExtras = new Bundle();
        extras.putString(Nodes.NODE_RESOURCE_PATH, holder.resourcePath);

        MetaServiceHelper.changePublicAccess(this, holder.resourcePath, false, mReceiver);
    }

    private void onFileShareLinkClicked(final FileViewHolder holder) {
        final String url = MetaUtilities.getPublicUrl(holder.resourcePath);
        UIUtil.shareLink(this, url);
    }

    private void buildDirectoryContextMenu(Menu menu, FileViewHolder viewHolder) {
        menu.add(Menu.NONE, R.id.context_download, 0, R.string.context_download);
        menu.add(Menu.NONE, R.id.context_rename, 10, R.string.context_rename);
        menu.add(Menu.NONE, R.id.context_delete, 11, R.string.context_delete);
    }

    private void buildFileContextMenu(Menu menu, FileViewHolder holder) {
        if (ResourceState.STATE_GETTING.equals(holder.resourceState)) {
            menu.add(Menu.NONE, R.id.context_cancel_download, 1, R.string.context_cancel_download);
        } else {
            menu.add(Menu.NONE, R.id.context_download, 1, R.string.context_download);
        }
        if (holder.isPublic) {
            menu.add(Menu.NONE, R.id.context_share_link, 1, R.string.context_share_link);
            menu.add(Menu.NONE, R.id.context_disable_link, 2, R.string.context_disable_link);
        } else {
            menu.add(Menu.NONE, R.id.context_create_link, 1, R.string.context_create_link);
        }
        menu.add(Menu.NONE, R.id.context_rename, 10, R.string.context_rename);
        menu.add(Menu.NONE, R.id.context_delete, 11, R.string.context_delete);
        if (!TextUtils.isEmpty(holder.data) && !holder.data.startsWith(ContentResolver.SCHEME_CONTENT)) {
            menu.add(Menu.NONE, R.id.context_delete_from_device, 12, R.string.context_delete_from_device);
        }
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        mDialogExtras = null;

        final AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
        final FileViewHolder holder = mContextViewHolder = (FileViewHolder) info.targetView.getTag();

        final boolean isVolume = holder.parentResourcePath == null;

        if (isVolume) {
            closeContextMenu();
        } else if (U1NodeKind.DIRECTORY == holder.kind) {
            buildDirectoryContextMenu(menu, holder);
        } else if (U1NodeKind.FILE == holder.kind) {
            buildFileContextMenu(menu, holder);
        }
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        final AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
        final FileViewHolder holder = mContextViewHolder = (FileViewHolder) info.targetView.getTag();
        final boolean isDirectory = U1NodeKind.DIRECTORY == holder.kind;

        if (isDirectory) {
            switch (item.getItemId()) {
            case R.id.context_download:
                onDirectoryDownloadClicked(holder);
                break;
            case R.id.context_rename:
                onDirectoryRenameClicked(holder);
                break;
            case R.id.context_delete:
                onDirectoryDeleteClicked(holder);
                break;
            }
        } else {
            switch (item.getItemId()) {
            case R.id.context_download:
                onFileDownloadClicked(holder);
                break;
            case R.id.context_cancel_download:
                onFileCancelDownloadClicked(holder);
                break;
            case R.id.context_create_link:
                onFileCreateLinkClicked(holder);
                break;
            case R.id.context_share_link:
                onFileShareLinkClicked(holder);
                break;
            case R.id.context_disable_link:
                onFileDisableLinkClicked(holder);
                break;
            case R.id.context_rename:
                onFileRenameClicked(holder);
                break;
            case R.id.context_delete:
                onFileDeleteClicked(holder);
                break;
            case R.id.context_delete_from_device:
                onFileDeleteFromDeviceClicked(holder);
                break;
            }
        }

        return super.onContextItemSelected(item);
    }

    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        Log.d(TAG, "onListItemClick() " + id);

        final FileViewHolder holder = (FileViewHolder) v.getTag();
        if (MetaUtilities.isDirectory(id)) {
            // This should avoid delayed update of the list-empty label.
            if (NetworkUtil.isConnected(this)) {
                awaitWithListEmptyTextView();
            }

            // Save the position of the list, just for the previous screen.
            mTopPosition = l.getFirstVisiblePosition();
            View topView = l.getChildAt(0);
            mTop = (topView == null) ? 0 : topView.getTop();
            // Change to selected directory.
            mAdapter.cd(this, holder.resourcePath);
            // Scroll the list to top when entering a directory.
            getListView().setSelectionFromTop(0, 0);
        } else {
            // Open selected file.
            if ((holder.mime.equals(FileUtilities.MIME_JPG) || holder.mime.equals(FileUtilities.MIME_JPEG))
                    && !TextUtils.isEmpty(holder.key)) {
                String resourcePath = mPathTracker.getCurrentNode();
                Intent gallery = new Intent(this, GalleryActivity.class);
                gallery.putExtra("directoryResourcePath", resourcePath);
                gallery.putExtra("firstImageKey", holder.key);
                startActivityForResult(gallery, REQUEST_PREVIEW);
            } else if (holder.data != null) {
                onFileClicked(holder.resourcePath, holder.resourceState, holder.filename, holder.data);
            } else {
                downloadFile(holder.resourcePath);
            }
        }
    }

    private void showSpinner() {
        mHandler.post(new Runnable() {
            public void run() {
                if (mLoaderItem != null)
                    mLoaderItem.setLoading(true);
            }
        });
    }

    private void hideSpinner() {
        mHandler.post(new Runnable() {
            public void run() {
                if (mLoaderItem != null) {
                    mLoaderItem.setLoading(false);
                }
            }
        });
    }

    private void awaitWithListEmptyTextView() {
        String loadingFiles = getString(R.string.files_activity_loading_files);
        if (mEmptyTextView != null && mEmptyTextView.getText().equals(loadingFiles)) {
            return;
        }
        mHandler.post(new Runnable() {
            public void run() {
                if (mEmptyTextView != null) {
                    mEmptyTextView.setText(R.string.files_activity_loading_files);
                }
                if (!NetworkUtil.isConnected(FilesActivity.this)) {
                    mEmptyTextView.setText(R.string.no_network);
                }
            }
        });
    }

    private void resetListEmptyTextView(final String resourcePath) {
        if (!NetworkUtil.isConnected(FilesActivity.this)) {
            mEmptyTextView.setText(R.string.no_network);
            return;
        }

        String directoryEmpty = getString(R.string.files_activity_directory_is_empty);
        if (mEmptyTextView != null && mEmptyTextView.getText().equals(directoryEmpty)) {
            return;
        }
        mHandler.post(new Runnable() {
            public void run() {
                String node = mPathTracker.getCurrentNode();
                if (resourcePath == null || node == null || resourcePath.equals(node)) {
                    mEmptyTextView.setText(R.string.files_activity_directory_is_empty);
                }
            }
        });
    }

    public void downloadFile(final String resourcePath) {
        if (NetworkUtil.isConnected(this)) {
            TransferUtils.dequeueByResourcePath(getContentResolver(), Downloads.CONTENT_URI, resourcePath);
            UpDownServiceHelper.download(this, resourcePath);
        } else {
            Toast.makeText(this, "Not connected.", Toast.LENGTH_SHORT).show();
        }
    }

    private void onFileClicked(final String resourcePath, final String resourceState, final String filename,
            final String data) {
        Log.d(TAG, "onFileClicked() data=" + data);

        final boolean isStateIdle = (resourceState == null);
        boolean isDownloading, isFailedDownload;
        isDownloading = isFailedDownload = false;
        if (!isStateIdle) {
            isDownloading = ResourceState.STATE_GETTING.equals(resourceState);
            isFailedDownload = ResourceState.STATE_GETTING_FAILED.equals(resourceState);
        }

        final boolean validTarget = MetaUtilities.isValidUriTarget(data);

        Log.d(TAG, String.format("isStateIdle=%s, validTarget=%s", String.valueOf(isStateIdle),
                String.valueOf(validTarget)));
        if (validTarget && isStateIdle) {
            // File exists and is not being downloaded.
            if (data.startsWith(ContentResolver.SCHEME_CONTENT)) {
                Log.d(TAG, "opening file from content uri");

                final Intent intent = new Intent(Intent.ACTION_VIEW);
                final Uri uri = Uri.parse(data);
                intent.setDataAndType(uri, FileUtilities.getMime(filename));

                final Intent chooser = new Intent(Intent.ACTION_VIEW);
                chooser.putExtra(Intent.EXTRA_INTENT, intent);
                chooser.putExtra(Intent.EXTRA_TITLE, getText(R.string.dialog_open_with_title));
                try {
                    startActivity(intent);
                } catch (ActivityNotFoundException e) {
                    UIUtil.showToast(this, R.string.toast_no_suitable_activity);
                }
            } else {
                Log.d(TAG, "opening file directly");
                File file = null;
                try {
                    Log.d(TAG, "opening " + data);
                    if (data.startsWith(ContentResolver.SCHEME_FILE)) {
                        file = new File(URI.create(data));
                    } else {
                        file = new File(data);
                    }
                } catch (Exception e) {
                    Log.e(TAG, "file uri is empty", e);
                }
                if (file != null && file.exists()) {
                    Log.d(TAG, "Resource exists, opening: " + file.getAbsolutePath());
                    Intent intent = new Intent(Intent.ACTION_VIEW);
                    intent.setDataAndType(Uri.fromFile(file), FileUtilities.getMime(data));

                    Intent chooser = new Intent(Intent.ACTION_VIEW);
                    chooser.putExtra(Intent.EXTRA_INTENT, intent);
                    chooser.putExtra(Intent.EXTRA_TITLE, getText(R.string.dialog_open_with_title));
                    try {
                        startActivity(intent);
                    } catch (ActivityNotFoundException e) {
                        UIUtil.showToast(this, R.string.toast_no_suitable_activity);
                    }
                } else {
                    Log.d(TAG, "Setting resource as not cached: " + resourcePath);
                    MetaUtilities.setIsCached(resourcePath, false);
                    MetaUtilities.setStateAndData(resourcePath, null, null);
                    UIUtil.showToast(this, R.string.toast_file_not_cached_anymore, false);
                    mResolver.notifyChange(Nodes.CONTENT_URI, null);
                }
            }
        } else if (isFailedDownload) {
            // File does not exist or download failed.
            Log.d(TAG, "was: failed download or invalid");
            if (NetworkUtil.isConnected(this)) {
                downloadFile(resourcePath);
            } else {
                UIUtil.showToast(this, R.string.toast_no_network);
            }
        } else if (isStateIdle && !validTarget) {
            // File removed from device, need to download.
            downloadFile(resourcePath);
        } else if (isDownloading) {
            UIUtil.showToast(this, "Please wait while downloading...");
        } else {
            Log.e(TAG, "unhandled state: " + resourceState);
        }
    }

    private Cursor getFilesCurosor(final String resourcePath) {
        Cursor filesCursor = null;
        if (resourcePath != null) {
            filesCursor = MetaUtilities.getVisibleNodesCursorByParent(resourcePath);
            startManagingCursor(filesCursor);
        }
        return filesCursor;
    }

    @Override
    public void onDownloadCached(final String key, String path) {
        final FileViewHolder holder = holders.get(key);
        if (holder != null) {
            final Bitmap bitmap = BitmapUtilities.decodeFile(new File(path), U1CroppedImageDownloader.SIZE_SMALL);
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    if (holder.icon != null && key.equals(holder.icon.getTag())) {
                        holder.icon.setImageDrawable(new BitmapDrawable(bitmap));
                    }
                }
            });
        }
    }

    @Override
    public void onDownloadStarted(String key) {
        // Do nothing.
    }

    @Override
    public void onDownloadProgress(String key, long bytes, long total) {
        // Do nothing.
    }

    @Override
    public void onDownloadSuccess(final String key, String path) {
        final FileViewHolder holder = holders.get(key);
        if (holder != null) {
            final Bitmap bitmap = BitmapUtilities.decodeFile(new File(path), U1CroppedImageDownloader.SIZE_SMALL);
            holders.remove(key);
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    if (holder.icon != null && key.equals(holder.icon.getTag())) {
                        holder.icon.setImageDrawable(new BitmapDrawable(bitmap));
                    }
                }
            });
        }
    }

    @Override
    public void onDownloadFailure(String key, U1Failure failure) {
        // Do nothing.
        /*
        if (failure.getStatusCode() == 302) {
           runOnUiThread(new Runnable() {
        @Override
        public void run() {
           Toast.makeText(FilesActivity.this,
                 "302 PLEASE REPORT on #u1-internal", 4000).show();
        }
           });
        }
        */
    }

    private class FilesAdapter extends CursorAdapter {

        private LayoutInflater mInflater;

        private final static String PURCHASED_FROM_U1 = "Purchased from Ubuntu One";
        private final String PURCHASED_MUSIC = getString(R.string.node_is_purchased_music);

        public FilesAdapter(Context context, Cursor c, boolean autoRequery) {
            super(context, c, autoRequery);
            mInflater = LayoutInflater.from(context);
        }

        @Override
        public View newView(Context context, Cursor cursor, ViewGroup parent) {
            View view = mInflater.inflate(R.layout.list_file_row, parent, false);
            FileViewHolder holder = new FileViewHolder(view);
            view.setTag(holder);
            return view;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            FileViewHolder holder;
            View view = convertView;

            if (view == null) {
                view = newView(FilesActivity.this, null, parent);
            } else {
                holder = (FileViewHolder) view.getTag();
                if (holder != null && !TextUtils.isEmpty(holder.key)) {
                    mImageDownloader.cancel(holder.key);
                    holders.remove(holder.key);
                }
            }

            Cursor cursor = getCursor();
            cursor.moveToPosition(position);
            bindView(view, FilesActivity.this, cursor);
            return view;
        }

        @Override
        public void bindView(View view, final Context context, Cursor cursor) {
            view.setBackgroundResource(R.color.list_item_bg_local);

            final FileViewHolder holder = (FileViewHolder) view.getTag();

            holder.parentResourcePath = cursor.getString(cursor.getColumnIndex(Nodes.NODE_PARENT_PATH));
            holder.resourcePath = cursor.getString(cursor.getColumnIndex(Nodes.NODE_RESOURCE_PATH));
            holder.resourceState = cursor.getString(cursor.getColumnIndex(Nodes.NODE_RESOURCE_STATE));
            holder.mime = cursor.getString(cursor.getColumnIndex(Nodes.NODE_MIME));
            holder.key = cursor.getString(cursor.getColumnIndex(Nodes.NODE_KEY));
            holder.data = cursor.getString(cursor.getColumnIndex(Nodes.NODE_DATA));

            final String url = cursor.getString(cursor.getColumnIndex(Nodes.NODE_PUBLIC_URL));
            final boolean isPublic = !(url == null || "".equals(url));
            holder.isPublic = isPublic;
            int fileNameColor = isPublic ? context.getResources().getColor(R.color.text_blue)
                    : context.getResources().getColor(R.color.text_red);
            holder.itemName.setTextColor(fileNameColor);

            final boolean isVolume = holder.parentResourcePath == null;
            final boolean isDirectory = MetaUtilities.isDirectory(cursor);
            if (isDirectory) {
                holder.kind = U1NodeKind.DIRECTORY;
                holder.icon.setTag(null);
                holder.icon.setImageResource(R.drawable.ic_folder);
            } else {
                holder.kind = U1NodeKind.FILE;
                String mime = cursor.getString(cursor.getColumnIndex(Nodes.NODE_MIME));
                if (TextUtils.isEmpty(mime))
                    holder.icon.setImageResource(R.drawable.ic_file);
                else if (holder.key != null && (holder.icon.getTag() == null)
                        || !holder.key.equals(holder.icon.getTag())) {
                    if (!isPublic) {
                        if (mime.startsWith(FileUtilities.MIME_IMAGE)) {
                            getThumbnailForJpegDelayed(mime, holder);
                        } else if (mime.startsWith(FileUtilities.MIME_VIDEO))
                            holder.icon.setImageResource(R.drawable.ic_video);
                        else if (mime.startsWith(FileUtilities.MIME_AUDIO))
                            holder.icon.setImageResource(R.drawable.ic_audio);
                        else
                            holder.icon.setImageResource(R.drawable.ic_file);
                    } else {
                        if (mime.startsWith(FileUtilities.MIME_IMAGE)) {
                            getThumbnailForJpegDelayed(mime, holder);
                        } else if (mime.startsWith(FileUtilities.MIME_VIDEO))
                            holder.icon.setImageResource(R.drawable.ic_video_published);
                        else if (mime.startsWith(FileUtilities.MIME_AUDIO))
                            holder.icon.setImageResource(R.drawable.ic_audio_published);
                        else
                            holder.icon.setImageResource(R.drawable.ic_file_published);
                    }
                }
            }

            final String filename = cursor.getString(cursor.getColumnIndex(Nodes.NODE_NAME));
            holder.filename = filename;
            if (isVolume) {
                if (filename.equals(PURCHASED_FROM_U1))
                    holder.itemName.setText(PURCHASED_MUSIC);
                else
                    holder.itemName.setText(holder.resourcePath.substring(3));
            } else {
                holder.itemName.setText(filename);
            }

            holder.itemInteger.setText("");
            if (!isDirectory) {
                final long size = cursor.getLong(cursor.getColumnIndex(Nodes.NODE_SIZE));
                final String sizeText = FileUtilities.getHumanReadableSize(size);
                holder.itemInteger.setText(sizeText);
            } else {
                final boolean hasChildren = cursor.getInt(cursor.getColumnIndex(Nodes.NODE_HAS_CHILDREN)) == 1;
                if (hasChildren) {
                    holder.itemInteger.setText("...");
                }
            }

            final String state = cursor.getString(cursor.getColumnIndex(Nodes.NODE_RESOURCE_STATE));
            if (!isDirectory && (TextUtils.isEmpty(holder.data))) {
                view.setBackgroundResource(R.color.list_item_bg_remote);
            }

            /*
             * I have reformatted the code to support viewing last modified
             * under directories as well, but untill we have deltas, it's not
             * so trivial (if anything has changed 1 level <i>down</i>, we
             * should update parent directory WHEN_CHANGE timestamp).
             */

            if (isVolume || isDirectory) {
                holder.itemTimestamp.setVisibility(View.GONE);
            } else {
                holder.itemTimestamp.setVisibility(View.VISIBLE);
                if (state == null) {
                    // Display last modification time.
                    final long modified = cursor.getLong(cursor.getColumnIndex(Nodes.NODE_WHEN_CHANGED));
                    if (modified < 1000) {
                        // Attempt to update the UI to quickly. This will be fixed
                        // by updating cache directly from upload response NodeInfo.
                        holder.itemTimestamp.setText("");
                    } else {
                        final String modifiedText = String.format(getString(R.string.node_last_modified),
                                DateUtilities.getFriendlyDate(context, modified));
                        holder.itemTimestamp.setText(modifiedText);
                    }
                } else {
                    // Provide state feedback.
                    if (ResourceState.STATE_GETTING.equals(state)) {
                        holder.itemTimestamp.setText(R.string.node_is_downloading);
                    } else if (ResourceState.STATE_POSTING.equals(state)) {
                        holder.itemTimestamp.setText(R.string.node_is_uploading);
                    } else if (ResourceState.STATE_DELETING.equals(state)) {
                        holder.itemTimestamp.setText(R.string.node_is_deleting);
                    } else if (ResourceState.STATE_GETTING_FAILED.equals(state)) {
                        holder.itemTimestamp.setText(R.string.node_download_has_failed);
                    } else if (ResourceState.STATE_POSTING_FAILED.equals(state)) {
                        holder.itemTimestamp.setText(R.string.node_upload_has_failed);
                    }
                }
            }
        }

        private void getThumbnailForJpegDelayed(String mime, final FileViewHolder holder) {
            // Fixed FilesActivity.java:1868 java.lang.NullPointerException in 1.2.4
            // Fixed U1CroppedImageDownloader.java:34 java.lang.NullPointerException in 1.2.4
            if ((mime.equals(FileUtilities.MIME_JPG) || mime.equals(FileUtilities.MIME_JPEG))
                    && !TextUtils.isEmpty(holder.key)) {
                final String key = holder.key;
                holder.icon.setImageResource(R.drawable.ic_photo);
                holder.icon.setTag(key);

                boolean isCached = mImageDownloader.isThumbnailCached(key);
                if (!isCached) {
                    holder.icon.setImageResource(R.drawable.ic_photo);
                }
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (key.equals(holder.key)) {
                            holders.put(key, holder);
                            U1Node node = MetaUtilities.getNodeByKey(holder.key);
                            mImageDownloader.getThumbnail(node, FilesActivity.this);
                        }
                    }
                }, isCached ? 0 : 250);
            } else {
                holder.icon.setImageResource(R.drawable.ic_photo);
            }
        }

        /**
         * cd ..
         */
        public boolean cd(final Activity activity) {
            if (mPathTracker.isAtRoot()) {
                return false;
            }
            mPathTracker.cd();
            if (mPathTracker.isAtRoot()) {
                final Cursor filesCursor = MetaUtilities.getVisibleTopNodesCursor();
                Log.v(TAG, "changing cursor");
                changeCursor(filesCursor);
            } else {
                final String node = mPathTracker.getCurrentNode();
                final Cursor filesCursor = getFilesCurosor(node);
                Log.v(TAG, "changing cursor");
                changeCursor(filesCursor);
            }
            return true;
        }

        /**
         * cd into dir
         */
        public void cd(final Activity activity, final String resourcePath) {
            if (mPathTracker.isAtRoot()) {
                currentVolumeResourcePath = "/volumes" + resourcePath;
            }
            mPathTracker.cd(resourcePath);
            final Cursor filesCursor = getFilesCurosor(resourcePath);
            changeCursor(filesCursor);
            notifyDataSetChanged();

            onRefresh(resourcePath);
        }
    }

    private class SignOutBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Intent login = new Intent(LoginActivity.ACTION_SIGN_IN);
            startActivity(login);
            finish();
        }
    }

    public static void showFrom(Activity activity) {
        final Intent intent = new Intent(activity, FilesActivity.class);
        activity.startActivity(intent);
        activity.overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
    }
}