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 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 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 128 public void onStart() { 129 super.onStart(); 130 getLoaderManager().restartLoader(LOADER_RECENTS, getArguments(), mCallbacks); 131 } 132 133 @Override 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 162 public RecentsCreateLoader(Context context, RootsCache roots, State state) { 163 super(context, RecentsProvider.buildRecent()); 164 mRoots = roots; 165 mState = state; 166 } 167 168 @Override 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; 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 213 DocumentStack getItem(int position) { 214 return mItems.get(position); 215 } 216 217 @Override 218 public int getItemCount() { 219 return mItems == null ? 0 : mItems.size(); 220 } 221 222 boolean isEmpty() { 223 return mItems == null ? true : mItems.isEmpty(); 224 } 225 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 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 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 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