| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.documentsui; |
| |
| import static com.android.documentsui.Shared.TAG; |
| |
| import android.app.Fragment; |
| import android.app.FragmentManager; |
| import android.app.FragmentTransaction; |
| import android.app.LoaderManager.LoaderCallbacks; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Loader; |
| import android.database.Cursor; |
| import android.graphics.drawable.Drawable; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.CancellationSignal; |
| import android.support.annotation.Nullable; |
| import android.support.v7.widget.LinearLayoutManager; |
| import android.support.v7.widget.RecyclerView; |
| import android.text.Spannable; |
| import android.text.SpannableStringBuilder; |
| import android.text.TextUtils.TruncateAt; |
| import android.text.style.ImageSpan; |
| import android.util.Log; |
| import android.view.LayoutInflater; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.ImageView; |
| import android.widget.TextView; |
| |
| import com.android.documentsui.RecentsProvider.RecentColumns; |
| import com.android.documentsui.model.DocumentStack; |
| import com.android.documentsui.model.DurableUtils; |
| import com.android.documentsui.model.RootInfo; |
| |
| import libcore.io.IoUtils; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| |
| /** |
| * Display directories where recent creates took place. |
| */ |
| public class RecentsCreateFragment extends Fragment { |
| |
| private View mEmptyView; |
| private RecyclerView mRecView; |
| private DocumentStackAdapter mAdapter; |
| private LoaderCallbacks<List<DocumentStack>> mCallbacks; |
| |
| private static final int LOADER_RECENTS = 3; |
| |
| public static void show(FragmentManager fm) { |
| final RecentsCreateFragment fragment = new RecentsCreateFragment(); |
| final FragmentTransaction ft = fm.beginTransaction(); |
| ft.replace(R.id.container_directory, fragment); |
| ft.commitAllowingStateLoss(); |
| } |
| |
| @Override |
| public View onCreateView( |
| LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { |
| final Context context = inflater.getContext(); |
| |
| final View view = inflater.inflate(R.layout.fragment_directory, container, false); |
| |
| mRecView = (RecyclerView) view.findViewById(R.id.list); |
| mRecView.setLayoutManager(new LinearLayoutManager(getContext())); |
| mRecView.addOnItemTouchListener(mItemListener); |
| |
| mEmptyView = view.findViewById(android.R.id.empty); |
| |
| mAdapter = new DocumentStackAdapter(); |
| mRecView.setAdapter(mAdapter); |
| |
| final RootsCache roots = DocumentsApplication.getRootsCache(context); |
| final State state = ((BaseActivity) getActivity()).getDisplayState(); |
| |
| mCallbacks = new LoaderCallbacks<List<DocumentStack>>() { |
| @Override |
| public Loader<List<DocumentStack>> onCreateLoader(int id, Bundle args) { |
| return new RecentsCreateLoader(context, roots, state); |
| } |
| |
| @Override |
| public void onLoadFinished( |
| Loader<List<DocumentStack>> loader, List<DocumentStack> data) { |
| mAdapter.update(data); |
| |
| // When launched into empty recents, show drawer |
| if (mAdapter.isEmpty() && !state.stackTouched && |
| context instanceof DocumentsActivity) { |
| ((DocumentsActivity) context).setRootsDrawerOpen(true); |
| } |
| } |
| |
| @Override |
| public void onLoaderReset(Loader<List<DocumentStack>> loader) { |
| mAdapter.update(null); |
| } |
| }; |
| |
| return view; |
| } |
| |
| @Override |
| public void onStart() { |
| super.onStart(); |
| getLoaderManager().restartLoader(LOADER_RECENTS, getArguments(), mCallbacks); |
| } |
| |
| @Override |
| public void onStop() { |
| super.onStop(); |
| getLoaderManager().destroyLoader(LOADER_RECENTS); |
| } |
| |
| private RecyclerView.OnItemTouchListener mItemListener = |
| new RecyclerView.OnItemTouchListener() { |
| @Override |
| public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { |
| Events.MotionInputEvent event = new Events.MotionInputEvent(e, mRecView); |
| if (event.isOverItem() && event.isActionUp()) { |
| final DocumentStack stack = mAdapter.getItem(event.getItemPosition()); |
| ((BaseActivity) getActivity()).onStackPicked(stack); |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public void onTouchEvent(RecyclerView rv, MotionEvent e) {} |
| @Override |
| public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {} |
| }; |
| |
| public static class RecentsCreateLoader extends UriDerivativeLoader<Uri, List<DocumentStack>> { |
| private final RootsCache mRoots; |
| private final State mState; |
| |
| public RecentsCreateLoader(Context context, RootsCache roots, State state) { |
| super(context, RecentsProvider.buildRecent()); |
| mRoots = roots; |
| mState = state; |
| } |
| |
| @Override |
| public List<DocumentStack> loadInBackground(Uri uri, CancellationSignal signal) { |
| final Collection<RootInfo> matchingRoots = mRoots.getMatchingRootsBlocking(mState); |
| final ArrayList<DocumentStack> result = new ArrayList<>(); |
| |
| final ContentResolver resolver = getContext().getContentResolver(); |
| final Cursor cursor = resolver.query( |
| uri, null, null, null, RecentColumns.TIMESTAMP + " DESC", signal); |
| try { |
| while (cursor != null && cursor.moveToNext()) { |
| final byte[] rawStack = cursor.getBlob( |
| cursor.getColumnIndex(RecentColumns.STACK)); |
| try { |
| final DocumentStack stack = new DocumentStack(); |
| DurableUtils.readFromArray(rawStack, stack); |
| |
| // Only update root here to avoid spinning up all |
| // providers; we update the stack during the actual |
| // restore. This also filters away roots that don't |
| // match current filter. |
| stack.updateRoot(matchingRoots); |
| result.add(stack); |
| } catch (IOException e) { |
| Log.w(TAG, "Failed to resolve stack: " + e); |
| } |
| } |
| } finally { |
| IoUtils.closeQuietly(cursor); |
| } |
| |
| return result; |
| } |
| } |
| |
| private static final class StackHolder extends RecyclerView.ViewHolder { |
| public View view; |
| public StackHolder(View view) { |
| super(view); |
| this.view = view; |
| } |
| } |
| |
| private class DocumentStackAdapter extends RecyclerView.Adapter<StackHolder> { |
| @Nullable private List<DocumentStack> mItems; |
| |
| DocumentStack getItem(int position) { |
| return mItems.get(position); |
| } |
| |
| @Override |
| public int getItemCount() { |
| return mItems == null ? 0 : mItems.size(); |
| } |
| |
| boolean isEmpty() { |
| return mItems == null ? true : mItems.isEmpty(); |
| } |
| |
| void update(@Nullable List<DocumentStack> items) { |
| mItems = items; |
| |
| if (isEmpty()) { |
| mEmptyView.setVisibility(View.VISIBLE); |
| } else { |
| mEmptyView.setVisibility(View.GONE); |
| } |
| |
| notifyDataSetChanged(); |
| } |
| |
| @Override |
| public StackHolder onCreateViewHolder(ViewGroup parent, int viewType) { |
| final Context context = parent.getContext(); |
| |
| final LayoutInflater inflater = LayoutInflater.from(context); |
| return new StackHolder( |
| (View) inflater.inflate(R.layout.item_doc_list, parent, false)); |
| } |
| |
| @Override |
| public void onBindViewHolder(StackHolder holder, int position) { |
| Context context = getContext(); |
| View view = holder.view; |
| |
| final ImageView iconMime = (ImageView) view.findViewById(R.id.icon_mime); |
| final TextView title = (TextView) view.findViewById(android.R.id.title); |
| final View line2 = view.findViewById(R.id.line2); |
| |
| final DocumentStack stack = getItem(position); |
| iconMime.setImageDrawable(stack.root.loadIcon(context)); |
| |
| final Drawable crumb = context.getDrawable(R.drawable.ic_breadcrumb_arrow); |
| crumb.setBounds(0, 0, crumb.getIntrinsicWidth(), crumb.getIntrinsicHeight()); |
| |
| final SpannableStringBuilder builder = new SpannableStringBuilder(); |
| builder.append(stack.root.title); |
| for (int i = stack.size() - 2; i >= 0; i--) { |
| appendDrawable(builder, crumb); |
| builder.append(stack.get(i).displayName); |
| } |
| title.setText(builder); |
| title.setEllipsize(TruncateAt.MIDDLE); |
| |
| if (line2 != null) line2.setVisibility(View.GONE); |
| } |
| } |
| |
| private static void appendDrawable(SpannableStringBuilder b, Drawable d) { |
| final int length = b.length(); |
| b.append("\u232a"); |
| b.setSpan(new ImageSpan(d), length, b.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); |
| } |
| } |