• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.documentsui;
18 
19 import static com.android.documentsui.Shared.TAG;
20 import static com.android.documentsui.State.ACTION_CREATE;
21 
22 import android.app.Fragment;
23 import android.app.FragmentManager;
24 import android.app.FragmentTransaction;
25 import android.app.LoaderManager.LoaderCallbacks;
26 import android.content.ContentResolver;
27 import android.content.Context;
28 import android.content.Loader;
29 import android.database.Cursor;
30 import android.graphics.drawable.Drawable;
31 import android.net.Uri;
32 import android.os.Bundle;
33 import android.os.CancellationSignal;
34 import android.support.annotation.Nullable;
35 import android.support.v7.widget.LinearLayoutManager;
36 import android.support.v7.widget.RecyclerView;
37 import android.text.Spannable;
38 import android.text.SpannableStringBuilder;
39 import android.text.TextUtils.TruncateAt;
40 import android.text.style.ImageSpan;
41 import android.util.Log;
42 import android.view.LayoutInflater;
43 import android.view.MotionEvent;
44 import android.view.View;
45 import android.view.ViewGroup;
46 import android.widget.ImageView;
47 import android.widget.TextView;
48 
49 import com.android.documentsui.RecentsProvider.RecentColumns;
50 import com.android.documentsui.model.DocumentStack;
51 import com.android.documentsui.model.DurableUtils;
52 import com.android.documentsui.model.RootInfo;
53 
54 import libcore.io.IoUtils;
55 
56 import java.io.IOException;
57 import java.util.ArrayList;
58 import java.util.Collection;
59 import java.util.List;
60 
61 /**
62  * Display directories where recent creates took place.
63  */
64 public class RecentsCreateFragment extends Fragment {
65 
66     private View mEmptyView;
67     private RecyclerView mRecView;
68     private DocumentStackAdapter mAdapter;
69     private LoaderCallbacks<List<DocumentStack>> mCallbacks;
70 
71     private static final int LOADER_RECENTS = 3;
72 
show(FragmentManager fm)73     public static void show(FragmentManager fm) {
74         final RecentsCreateFragment fragment = new RecentsCreateFragment();
75         final FragmentTransaction ft = fm.beginTransaction();
76         ft.replace(R.id.container_directory, fragment);
77         ft.commitAllowingStateLoss();
78     }
79 
80     @Override
onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)81     public View onCreateView(
82             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
83         final Context context = inflater.getContext();
84 
85         final View view = inflater.inflate(R.layout.fragment_directory, container, false);
86 
87         mRecView = (RecyclerView) view.findViewById(R.id.dir_list);
88         mRecView.setLayoutManager(new LinearLayoutManager(getContext()));
89         mRecView.addOnItemTouchListener(mItemListener);
90 
91         mEmptyView = view.findViewById(android.R.id.empty);
92 
93         mAdapter = new DocumentStackAdapter();
94         mRecView.setAdapter(mAdapter);
95 
96         final RootsCache roots = DocumentsApplication.getRootsCache(context);
97         final State state = ((BaseActivity) getActivity()).getDisplayState();
98 
99         mCallbacks = new LoaderCallbacks<List<DocumentStack>>() {
100             @Override
101             public Loader<List<DocumentStack>> onCreateLoader(int id, Bundle args) {
102                 return new RecentsCreateLoader(context, roots, state);
103             }
104 
105             @Override
106             public void onLoadFinished(
107                     Loader<List<DocumentStack>> loader, List<DocumentStack> data) {
108                 mAdapter.update(data);
109 
110                 // When launched into empty recents, show drawer
111                 if (mAdapter.isEmpty() && !state.hasLocationChanged()
112                         && state.action != ACTION_CREATE
113                         && context instanceof DocumentsActivity) {
114                     ((DocumentsActivity) context).setRootsDrawerOpen(true);
115                 }
116             }
117 
118             @Override
119             public void onLoaderReset(Loader<List<DocumentStack>> loader) {
120                 mAdapter.update(null);
121             }
122         };
123 
124         return view;
125     }
126 
127     @Override
onStart()128     public void onStart() {
129         super.onStart();
130         getLoaderManager().restartLoader(LOADER_RECENTS, getArguments(), mCallbacks);
131     }
132 
133     @Override
onStop()134     public void onStop() {
135         super.onStop();
136         getLoaderManager().destroyLoader(LOADER_RECENTS);
137     }
138 
139     private RecyclerView.OnItemTouchListener mItemListener =
140             new RecyclerView.OnItemTouchListener() {
141                 @Override
142                 public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
143                     Events.MotionInputEvent event = new Events.MotionInputEvent(e, mRecView);
144                     if (event.isOverItem() && event.isActionUp()) {
145                         final DocumentStack stack = mAdapter.getItem(event.getItemPosition());
146                         ((BaseActivity) getActivity()).onStackPicked(stack);
147                         return true;
148                     }
149                     return false;
150                 }
151 
152                 @Override
153                 public void onTouchEvent(RecyclerView rv, MotionEvent e) {}
154                 @Override
155                 public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
156             };
157 
158     public static class RecentsCreateLoader extends UriDerivativeLoader<Uri, List<DocumentStack>> {
159         private final RootsCache mRoots;
160         private final State mState;
161 
RecentsCreateLoader(Context context, RootsCache roots, State state)162         public RecentsCreateLoader(Context context, RootsCache roots, State state) {
163             super(context, RecentsProvider.buildRecent());
164             mRoots = roots;
165             mState = state;
166         }
167 
168         @Override
loadInBackground(Uri uri, CancellationSignal signal)169         public List<DocumentStack> loadInBackground(Uri uri, CancellationSignal signal) {
170             final Collection<RootInfo> matchingRoots = mRoots.getMatchingRootsBlocking(mState);
171             final ArrayList<DocumentStack> result = new ArrayList<>();
172 
173             final ContentResolver resolver = getContext().getContentResolver();
174             final Cursor cursor = resolver.query(
175                     uri, null, null, null, RecentColumns.TIMESTAMP + " DESC", signal);
176             try {
177                 while (cursor != null && cursor.moveToNext()) {
178                     final byte[] rawStack = cursor.getBlob(
179                             cursor.getColumnIndex(RecentColumns.STACK));
180                     try {
181                         final DocumentStack stack = new DocumentStack();
182                         DurableUtils.readFromArray(rawStack, stack);
183 
184                         // Only update root here to avoid spinning up all
185                         // providers; we update the stack during the actual
186                         // restore. This also filters away roots that don't
187                         // match current filter.
188                         stack.updateRoot(matchingRoots);
189                         result.add(stack);
190                     } catch (IOException e) {
191                         Log.w(TAG, "Failed to resolve stack: " + e);
192                     }
193                 }
194             } finally {
195                 IoUtils.closeQuietly(cursor);
196             }
197 
198             return result;
199         }
200     }
201 
202     private static final class StackHolder extends RecyclerView.ViewHolder {
203         public View view;
StackHolder(View view)204         public StackHolder(View view) {
205             super(view);
206             this.view = view;
207         }
208     }
209 
210     private class DocumentStackAdapter extends RecyclerView.Adapter<StackHolder> {
211         @Nullable private List<DocumentStack> mItems;
212 
getItem(int position)213         DocumentStack getItem(int position) {
214             return mItems.get(position);
215         }
216 
217         @Override
getItemCount()218         public int getItemCount() {
219             return mItems == null ? 0 : mItems.size();
220         }
221 
isEmpty()222         boolean isEmpty() {
223             return mItems == null ? true : mItems.isEmpty();
224         }
225 
update(@ullable List<DocumentStack> items)226         void update(@Nullable List<DocumentStack> items) {
227             mItems = items;
228 
229             if (isEmpty()) {
230                 mEmptyView.setVisibility(View.VISIBLE);
231             } else {
232                 mEmptyView.setVisibility(View.GONE);
233             }
234 
235             notifyDataSetChanged();
236         }
237 
238         @Override
onCreateViewHolder(ViewGroup parent, int viewType)239         public StackHolder onCreateViewHolder(ViewGroup parent, int viewType) {
240           final Context context = parent.getContext();
241 
242           final LayoutInflater inflater = LayoutInflater.from(context);
243           return new StackHolder(
244                   (View) inflater.inflate(R.layout.item_doc_list, parent, false));
245         }
246 
247         @Override
onBindViewHolder(StackHolder holder, int position)248         public void onBindViewHolder(StackHolder holder, int position) {
249             Context context = getContext();
250             View view = holder.view;
251 
252             final ImageView iconMime = (ImageView) view.findViewById(R.id.icon_mime);
253             final TextView title = (TextView) view.findViewById(android.R.id.title);
254             final View line2 = view.findViewById(R.id.line2);
255 
256             final DocumentStack stack = getItem(position);
257             iconMime.setImageDrawable(stack.root.loadIcon(context));
258 
259             final Drawable crumb = context.getDrawable(R.drawable.ic_breadcrumb_arrow);
260             crumb.setBounds(0, 0, crumb.getIntrinsicWidth(), crumb.getIntrinsicHeight());
261 
262             final SpannableStringBuilder builder = new SpannableStringBuilder();
263             builder.append(stack.root.title);
264             for (int i = stack.size() - 2; i >= 0; i--) {
265                 appendDrawable(builder, crumb);
266                 builder.append(stack.get(i).displayName);
267             }
268             title.setText(builder);
269             title.setEllipsize(TruncateAt.MIDDLE);
270 
271             if (line2 != null) line2.setVisibility(View.GONE);
272         }
273     }
274 
appendDrawable(SpannableStringBuilder b, Drawable d)275     private static void appendDrawable(SpannableStringBuilder b, Drawable d) {
276         final int length = b.length();
277         b.append("\u232a");
278         b.setSpan(new ImageSpan(d), length, b.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
279     }
280 }
281