Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a56104d7 authored by Steve McKay's avatar Steve McKay Committed by Android (Google) Code Review
Browse files

Merge "Reduce code duplication between DocumentsActivity and StandaloneActivity."

parents 4ff26172 ef3e2cf3
Loading
Loading
Loading
Loading
+525 −12
Original line number Diff line number Diff line
@@ -16,17 +16,47 @@

package com.android.documentsui;

import static com.android.documentsui.DirectoryFragment.ANIM_NONE;
import static com.android.documentsui.DirectoryFragment.ANIM_SIDE;
import static com.android.documentsui.DirectoryFragment.ANIM_UP;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Executor;

import libcore.io.IoUtils;
import android.app.Activity;
import android.app.Fragment;
import android.content.pm.ResolveInfo;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Root;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnActionExpandListener;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.SearchView;
import android.widget.SearchView.OnQueryTextListener;
import android.widget.TextView;

import com.android.documentsui.RecentsProvider.ResumeColumns;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.DurableUtils;
@@ -34,20 +64,125 @@ import com.android.documentsui.model.RootInfo;
import com.google.common.collect.Maps;

abstract class BaseActivity extends Activity {

    static final String EXTRA_STATE = "state";

    private final String mTag;
    RootsCache mRoots;

    public abstract State getDisplayState();
    public abstract RootInfo getCurrentRoot();
    public abstract void onStateChanged();
    public abstract void setRootsDrawerOpen(boolean open);
    public abstract void onDocumentPicked(DocumentInfo doc);
    public abstract void onDocumentsPicked(List<DocumentInfo> docs);
    public abstract DocumentInfo getCurrentDirectory();
    public abstract void setPending(boolean pending);
    public abstract void onStackPicked(DocumentStack stack);
    public abstract void onPickRequested(DocumentInfo pickTarget);
    public abstract void onAppPicked(ResolveInfo info);
    public abstract void onRootPicked(RootInfo root, boolean closeDrawer);
    public abstract void onSaveRequested(DocumentInfo replaceTarget);
    public abstract void onSaveRequested(String mimeType, String displayName);
    abstract void onTaskFinished(Uri... uris);
    abstract void onDirectoryChanged(int anim);
    abstract void updateActionBar();
    abstract void saveStackBlocking();

    public BaseActivity(String tag) {
        mTag = tag;
    }

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        mRoots = DocumentsApplication.getRootsCache(this);
    }

    void onStackRestored(boolean restored, boolean external) {}

    void onRootPicked(RootInfo root) {
        State state = getDisplayState();

        // Clear entire backstack and start in new root
        state.stack.root = root;
        state.stack.clear();
        state.stackTouched = true;

        if (!mRoots.isRecentsRoot(root)) {
            new PickRootTask(root).executeOnExecutor(getCurrentExecutor());
        } else {
            onCurrentDirectoryChanged(ANIM_SIDE);
        }
    }

    void expandMenus(Menu menu) {
        for (int i = 0; i < menu.size(); i++) {
            final MenuItem item = menu.getItem(i);
            switch (item.getItemId()) {
                case R.id.menu_advanced:
                case R.id.menu_file_size:
                    break;
                default:
                    item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
            }
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        final int id = item.getItemId();
        if (id == android.R.id.home) {
            onBackPressed();
            return true;
        } else if (id == R.id.menu_create_dir) {
            CreateDirectoryFragment.show(getFragmentManager());
            return true;
        } else if (id == R.id.menu_search) {
            return false;
        } else if (id == R.id.menu_sort_name) {
            setUserSortOrder(State.SORT_ORDER_DISPLAY_NAME);
            return true;
        } else if (id == R.id.menu_sort_date) {
            setUserSortOrder(State.SORT_ORDER_LAST_MODIFIED);
            return true;
        } else if (id == R.id.menu_sort_size) {
            setUserSortOrder(State.SORT_ORDER_SIZE);
            return true;
        } else if (id == R.id.menu_grid) {
            setUserMode(State.MODE_GRID);
            return true;
        } else if (id == R.id.menu_list) {
            setUserMode(State.MODE_LIST);
            return true;
        } else if (id == R.id.menu_advanced) {
            setDisplayAdvancedDevices(!LocalPreferences.getDisplayAdvancedDevices(this));
            return true;
        } else if (id == R.id.menu_file_size) {
            setDisplayFileSize(!LocalPreferences.getDisplayFileSize(this));
            return true;
        } else if (id == R.id.menu_settings) {
            final RootInfo root = getCurrentRoot();
            final Intent intent = new Intent(DocumentsContract.ACTION_DOCUMENT_ROOT_SETTINGS);
            intent.setDataAndType(DocumentsContract.buildRootUri(root.authority, root.rootId),
                    DocumentsContract.Root.MIME_TYPE_ITEM);
            startActivity(intent);
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    /**
     * Call this when directory changes. Prior to root fragment update
     * the (abstract) directoryChanged method will be called.
     * @param anim
     */
    final void onCurrentDirectoryChanged(int anim) {
        onDirectoryChanged(anim);

        final RootsFragment roots = RootsFragment.get(getFragmentManager());
        if (roots != null) {
            roots.onCurrentRootChanged();
        }

        updateActionBar();
        invalidateOptionsMenu();
    }

    final String getCallingPackageMaybeExtra() {
        final String extra = getIntent().getStringExtra(DocumentsContract.EXTRA_PACKAGE_NAME);
        return (extra != null) ? extra : getCallingPackage();
    }

    public static BaseActivity get(Fragment fragment) {
        return (BaseActivity) fragment.getActivity();
@@ -169,4 +304,382 @@ abstract class BaseActivity extends Activity {
            }
        };
    }

    void setDisplayAdvancedDevices(boolean display) {
        State state = getDisplayState();
        LocalPreferences.setDisplayAdvancedDevices(this, display);
        state.showAdvanced = state.forceAdvanced | display;
        RootsFragment.get(getFragmentManager()).onDisplayStateChanged();
        invalidateOptionsMenu();
    }

    void setDisplayFileSize(boolean display) {
        LocalPreferences.setDisplayFileSize(this, display);
        getDisplayState().showSize = display;
        DirectoryFragment.get(getFragmentManager()).onDisplayStateChanged();
        invalidateOptionsMenu();
    }

    void onStateChanged() {
        invalidateOptionsMenu();
    }

    /**
     * Set state sort order based on explicit user action.
     */
    void setUserSortOrder(int sortOrder) {
        getDisplayState().userSortOrder = sortOrder;
        DirectoryFragment.get(getFragmentManager()).onUserSortOrderChanged();
    }

    /**
     * Set state mode based on explicit user action.
     */
    void setUserMode(int mode) {
        getDisplayState().userMode = mode;
        DirectoryFragment.get(getFragmentManager()).onUserModeChanged();
    }

    void setPending(boolean pending) {
        final SaveFragment save = SaveFragment.get(getFragmentManager());
        if (save != null) {
            save.setPending(pending);
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle state) {
        super.onSaveInstanceState(state);
        state.putParcelable(EXTRA_STATE, getDisplayState());
    }

    @Override
    protected void onRestoreInstanceState(Bundle state) {
        super.onRestoreInstanceState(state);
    }

    RootInfo getCurrentRoot() {
        State state = getDisplayState();
        if (state.stack.root != null) {
            return state.stack.root;
        } else {
            return mRoots.getRecentsRoot();
        }
    }

    public DocumentInfo getCurrentDirectory() {
        return getDisplayState().stack.peek();
    }

    public Executor getCurrentExecutor() {
        final DocumentInfo cwd = getCurrentDirectory();
        if (cwd != null && cwd.authority != null) {
            return ProviderExecutor.forAuthority(cwd.authority);
        } else {
            return AsyncTask.THREAD_POOL_EXECUTOR;
        }
    }

    public void onStackPicked(DocumentStack stack) {
        try {
            // Update the restored stack to ensure we have freshest data
            stack.updateDocuments(getContentResolver());

            State state = getDisplayState();
            state.stack = stack;
            state.stackTouched = true;
            onCurrentDirectoryChanged(ANIM_SIDE);

        } catch (FileNotFoundException e) {
            Log.w(mTag, "Failed to restore stack: " + e);
        }
    }

    final class PickRootTask extends AsyncTask<Void, Void, DocumentInfo> {
        private RootInfo mRoot;

        public PickRootTask(RootInfo root) {
            mRoot = root;
        }

        @Override
        protected DocumentInfo doInBackground(Void... params) {
            try {
                final Uri uri = DocumentsContract.buildDocumentUri(
                        mRoot.authority, mRoot.documentId);
                return DocumentInfo.fromUri(getContentResolver(), uri);
            } catch (FileNotFoundException e) {
                Log.w(mTag, "Failed to find root", e);
                return null;
            }
        }

        @Override
        protected void onPostExecute(DocumentInfo result) {
            if (result != null) {
                State state = getDisplayState();
                state.stack.push(result);
                state.stackTouched = true;
                onCurrentDirectoryChanged(ANIM_SIDE);
            }
        }
    }

    final class RestoreStackTask extends AsyncTask<Void, Void, Void> {
        private volatile boolean mRestoredStack;
        private volatile boolean mExternal;

        @Override
        protected Void doInBackground(Void... params) {
            State state = getDisplayState();
            RootsCache roots = DocumentsApplication.getRootsCache(BaseActivity.this);

            // Restore last stack for calling package
            final String packageName = getCallingPackageMaybeExtra();
            final Cursor cursor = getContentResolver()
                    .query(RecentsProvider.buildResume(packageName), null, null, null, null);
            try {
                if (cursor.moveToFirst()) {
                    mExternal = cursor.getInt(cursor.getColumnIndex(ResumeColumns.EXTERNAL)) != 0;
                    final byte[] rawStack = cursor.getBlob(
                            cursor.getColumnIndex(ResumeColumns.STACK));
                    DurableUtils.readFromArray(rawStack, state.stack);
                    mRestoredStack = true;
                }
            } catch (IOException e) {
                Log.w(mTag, "Failed to resume: " + e);
            } finally {
                IoUtils.closeQuietly(cursor);
            }

            if (mRestoredStack) {
                // Update the restored stack to ensure we have freshest data
                final Collection<RootInfo> matchingRoots = roots.getMatchingRootsBlocking(state);
                try {
                    state.stack.updateRoot(matchingRoots);
                    state.stack.updateDocuments(getContentResolver());
                } catch (FileNotFoundException e) {
                    Log.w(mTag, "Failed to restore stack: " + e);
                    state.stack.reset();
                    mRestoredStack = false;
                }
            }

            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            if (isDestroyed()) return;
            getDisplayState().restored = true;
            onCurrentDirectoryChanged(ANIM_NONE);

            onStackRestored(mRestoredStack, mExternal);

            getDisplayState().restored = true;
            onCurrentDirectoryChanged(ANIM_NONE);
        }
    }

    final class ItemSelectedListener implements OnItemSelectedListener {

        boolean mIgnoreNextNavigation;

        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            if (mIgnoreNextNavigation) {
                mIgnoreNextNavigation = false;
                return;
            }

            State state = getDisplayState();
            while (state.stack.size() > position + 1) {
                state.stackTouched = true;
                state.stack.pop();
            }
            onCurrentDirectoryChanged(ANIM_UP);
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {
            // Ignored
        }
    }

    /**
     * Class providing toolbar with runtime access to useful activity data.
     */
    final class StackAdapter extends BaseAdapter {
        @Override
        public int getCount() {
            return getDisplayState().stack.size();
        }

        @Override
        public DocumentInfo getItem(int position) {
            State state = getDisplayState();
            return state.stack.get(state.stack.size() - position - 1);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if (convertView == null) {
                convertView = LayoutInflater.from(parent.getContext())
                        .inflate(R.layout.item_subdir_title, parent, false);
            }

            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
            final DocumentInfo doc = getItem(position);

            if (position == 0) {
                final RootInfo root = getCurrentRoot();
                title.setText(root.title);
            } else {
                title.setText(doc.displayName);
            }

            return convertView;
        }

        @Override
        public View getDropDownView(int position, View convertView, ViewGroup parent) {
            if (convertView == null) {
                convertView = LayoutInflater.from(parent.getContext())
                        .inflate(R.layout.item_subdir, parent, false);
            }

            final ImageView subdir = (ImageView) convertView.findViewById(R.id.subdir);
            final TextView title = (TextView) convertView.findViewById(android.R.id.title);
            final DocumentInfo doc = getItem(position);

            if (position == 0) {
                final RootInfo root = getCurrentRoot();
                title.setText(root.title);
                subdir.setVisibility(View.GONE);
            } else {
                title.setText(doc.displayName);
                subdir.setVisibility(View.VISIBLE);
            }

            return convertView;
        }
    }

    /**
     * Facade over the various search parts in the menu.
     */
    final class SearchManager implements
            SearchView.OnCloseListener, OnActionExpandListener, OnQueryTextListener {

        protected boolean mSearchExpanded;
        protected boolean mIgnoreNextClose;
        protected boolean mIgnoreNextCollapse;

        private MenuItem mMenu;
        private SearchView mView;

        public void install(MenuItem menu) {
            assert(mMenu == null);
            mMenu = menu;
            mView = (SearchView) menu.getActionView();

            mMenu.setOnActionExpandListener(this);
            mView.setOnQueryTextListener(this);
            mView.setOnCloseListener(this);
        }

        /**
         * @param root Info about the current directory.
         */
        void update(RootInfo root) {
            if (mMenu == null) {
                Log.d(mTag, "showMenu called before Search MenuItem installed.");
                return;
            }
            State state = getDisplayState();
            if (state.currentSearch != null) {
                mMenu.expandActionView();

                mView.setIconified(false);
                mView.clearFocus();
                mView.setQuery(state.currentSearch, false);
            } else {
                mIgnoreNextClose = true;
                mView.setIconified(true);
                mView.clearFocus();

                mIgnoreNextCollapse = true;
                mMenu.collapseActionView();
            }

            showMenu(root != null
                    && ((root.flags & Root.FLAG_SUPPORTS_SEARCH) != 0));
        }

        void showMenu(boolean visible) {
            if (mMenu == null) {
                Log.d(mTag, "showMenu called before Search MenuItem installed.");
                return;
            }
            mMenu.setVisible(visible);
        }

        boolean isSearching() {
            return getDisplayState().currentSearch != null;
        }

        boolean isExpanded() {
            return mSearchExpanded;
        }

        @Override
        public boolean onClose() {
            mSearchExpanded = false;
            if (mIgnoreNextClose) {
                mIgnoreNextClose = false;
                return false;
            }

            getDisplayState().currentSearch = null;
            onCurrentDirectoryChanged(ANIM_NONE);
            return false;
        }
        @Override
        public boolean onMenuItemActionExpand(MenuItem item) {
            mSearchExpanded = true;
            updateActionBar();
            return true;
        }

        @Override
        public boolean onMenuItemActionCollapse(MenuItem item) {
            mSearchExpanded = false;
            if (mIgnoreNextCollapse) {
                mIgnoreNextCollapse = false;
                return true;
            }

            getDisplayState().currentSearch = null;
            onCurrentDirectoryChanged(ANIM_NONE);
            return true;
        }
        @Override
        public boolean onQueryTextSubmit(String query) {
            mSearchExpanded = true;
            getDisplayState().currentSearch = query;
            mView.clearFocus();
            onCurrentDirectoryChanged(ANIM_NONE);
            return true;
        }

        @Override
        public boolean onQueryTextChange(String newText) {
            return false;
        }
    }
}
+3 −3
Original line number Diff line number Diff line
@@ -28,7 +28,6 @@ import static com.android.documentsui.DocumentsActivity.TAG;
import static com.android.documentsui.model.DocumentInfo.getCursorInt;
import static com.android.documentsui.model.DocumentInfo.getCursorLong;
import static com.android.documentsui.model.DocumentInfo.getCursorString;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.Fragment;
@@ -311,8 +310,9 @@ public class DirectoryFragment extends Fragment {
                updateDisplayState();

                // When launched into empty recents, show drawer
                if (mType == TYPE_RECENT_OPEN && mAdapter.isEmpty() && !state.stackTouched) {
                    ((BaseActivity) context).setRootsDrawerOpen(true);
                if (mType == TYPE_RECENT_OPEN && mAdapter.isEmpty() && !state.stackTouched &&
                        context instanceof DocumentsActivity) {
                    ((DocumentsActivity) context).setRootsDrawerOpen(true);
                }

                // Restore any previous instance state
+163 −582

File changed.

Preview size limit exceeded, changes collapsed.

+1 −1
Original line number Diff line number Diff line
@@ -75,7 +75,7 @@ public class PickFragment extends Fragment {
    private View.OnClickListener mPickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            final BaseActivity activity = BaseActivity.get(PickFragment.this);
            final DocumentsActivity activity = DocumentsActivity.get(PickFragment.this);
            activity.onPickRequested(mPickTarget);
        }
    };
+3 −2
Original line number Diff line number Diff line
@@ -109,8 +109,9 @@ public class RecentsCreateFragment extends Fragment {
                mAdapter.swapStacks(data);

                // When launched into empty recents, show drawer
                if (mAdapter.isEmpty() && !state.stackTouched) {
                    ((BaseActivity) context).setRootsDrawerOpen(true);
                if (mAdapter.isEmpty() && !state.stackTouched &&
                        context instanceof DocumentsActivity) {
                    ((DocumentsActivity) context).setRootsDrawerOpen(true);
                }
            }

Loading