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.DocumentsActivity.TAG; 20 21 import android.app.Fragment; 22 import android.app.FragmentManager; 23 import android.app.FragmentTransaction; 24 import android.app.LoaderManager.LoaderCallbacks; 25 import android.content.ContentResolver; 26 import android.content.Context; 27 import android.content.Loader; 28 import android.database.Cursor; 29 import android.graphics.drawable.Drawable; 30 import android.net.Uri; 31 import android.os.Bundle; 32 import android.os.CancellationSignal; 33 import android.text.Spannable; 34 import android.text.SpannableStringBuilder; 35 import android.text.TextUtils.TruncateAt; 36 import android.text.style.ImageSpan; 37 import android.util.Log; 38 import android.view.LayoutInflater; 39 import android.view.View; 40 import android.view.ViewGroup; 41 import android.widget.AdapterView; 42 import android.widget.AdapterView.OnItemClickListener; 43 import android.widget.BaseAdapter; 44 import android.widget.ImageView; 45 import android.widget.ListView; 46 import android.widget.TextView; 47 48 import com.android.documentsui.BaseActivity.State; 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 import com.google.android.collect.Lists; 54 55 import libcore.io.IoUtils; 56 57 import java.io.IOException; 58 import java.util.ArrayList; 59 import java.util.Collection; 60 import java.util.List; 61 62 /** 63 * Display directories where recent creates took place. 64 */ 65 public class RecentsCreateFragment extends Fragment { 66 67 private View mEmptyView; 68 private ListView mListView; 69 70 private DocumentStackAdapter mAdapter; 71 private LoaderCallbacks<List<DocumentStack>> mCallbacks; 72 73 private static final int LOADER_RECENTS = 3; 74 show(FragmentManager fm)75 public static void show(FragmentManager fm) { 76 final RecentsCreateFragment fragment = new RecentsCreateFragment(); 77 final FragmentTransaction ft = fm.beginTransaction(); 78 ft.replace(R.id.container_directory, fragment); 79 ft.commitAllowingStateLoss(); 80 } 81 82 @Override onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)83 public View onCreateView( 84 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 85 final Context context = inflater.getContext(); 86 87 final View view = inflater.inflate(R.layout.fragment_directory, container, false); 88 89 mEmptyView = view.findViewById(android.R.id.empty); 90 91 mListView = (ListView) view.findViewById(R.id.list); 92 mListView.setOnItemClickListener(mItemListener); 93 94 mAdapter = new DocumentStackAdapter(); 95 mListView.setAdapter(mAdapter); 96 97 final RootsCache roots = DocumentsApplication.getRootsCache(context); 98 final State state = ((BaseActivity) getActivity()).getDisplayState(); 99 100 mCallbacks = new LoaderCallbacks<List<DocumentStack>>() { 101 @Override 102 public Loader<List<DocumentStack>> onCreateLoader(int id, Bundle args) { 103 return new RecentsCreateLoader(context, roots, state); 104 } 105 106 @Override 107 public void onLoadFinished( 108 Loader<List<DocumentStack>> loader, List<DocumentStack> data) { 109 mAdapter.swapStacks(data); 110 111 // When launched into empty recents, show drawer 112 if (mAdapter.isEmpty() && !state.stackTouched && 113 context instanceof DocumentsActivity) { 114 ((DocumentsActivity) context).setRootsDrawerOpen(true); 115 } 116 } 117 118 @Override 119 public void onLoaderReset(Loader<List<DocumentStack>> loader) { 120 mAdapter.swapStacks(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 OnItemClickListener mItemListener = new OnItemClickListener() { 140 @Override 141 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 142 final DocumentStack stack = mAdapter.getItem(position); 143 ((BaseActivity) getActivity()).onStackPicked(stack); 144 } 145 }; 146 147 public static class RecentsCreateLoader extends UriDerivativeLoader<Uri, List<DocumentStack>> { 148 private final RootsCache mRoots; 149 private final State mState; 150 RecentsCreateLoader(Context context, RootsCache roots, State state)151 public RecentsCreateLoader(Context context, RootsCache roots, State state) { 152 super(context, RecentsProvider.buildRecent()); 153 mRoots = roots; 154 mState = state; 155 } 156 157 @Override loadInBackground(Uri uri, CancellationSignal signal)158 public List<DocumentStack> loadInBackground(Uri uri, CancellationSignal signal) { 159 final Collection<RootInfo> matchingRoots = mRoots.getMatchingRootsBlocking(mState); 160 final ArrayList<DocumentStack> result = Lists.newArrayList(); 161 162 final ContentResolver resolver = getContext().getContentResolver(); 163 final Cursor cursor = resolver.query( 164 uri, null, null, null, RecentColumns.TIMESTAMP + " DESC", signal); 165 try { 166 while (cursor != null && cursor.moveToNext()) { 167 final byte[] rawStack = cursor.getBlob( 168 cursor.getColumnIndex(RecentColumns.STACK)); 169 try { 170 final DocumentStack stack = new DocumentStack(); 171 DurableUtils.readFromArray(rawStack, stack); 172 173 // Only update root here to avoid spinning up all 174 // providers; we update the stack during the actual 175 // restore. This also filters away roots that don't 176 // match current filter. 177 stack.updateRoot(matchingRoots); 178 result.add(stack); 179 } catch (IOException e) { 180 Log.w(TAG, "Failed to resolve stack: " + e); 181 } 182 } 183 } finally { 184 IoUtils.closeQuietly(cursor); 185 } 186 187 return result; 188 } 189 } 190 191 private class DocumentStackAdapter extends BaseAdapter { 192 private List<DocumentStack> mStacks; 193 DocumentStackAdapter()194 public DocumentStackAdapter() { 195 } 196 swapStacks(List<DocumentStack> stacks)197 public void swapStacks(List<DocumentStack> stacks) { 198 mStacks = stacks; 199 200 if (isEmpty()) { 201 mEmptyView.setVisibility(View.VISIBLE); 202 } else { 203 mEmptyView.setVisibility(View.GONE); 204 } 205 206 notifyDataSetChanged(); 207 } 208 209 @Override getView(int position, View convertView, ViewGroup parent)210 public View getView(int position, View convertView, ViewGroup parent) { 211 final Context context = parent.getContext(); 212 213 if (convertView == null) { 214 final LayoutInflater inflater = LayoutInflater.from(context); 215 convertView = inflater.inflate(R.layout.item_doc_list, parent, false); 216 } 217 218 final ImageView iconMime = (ImageView) convertView.findViewById(R.id.icon_mime); 219 final TextView title = (TextView) convertView.findViewById(android.R.id.title); 220 final View line2 = convertView.findViewById(R.id.line2); 221 222 final DocumentStack stack = getItem(position); 223 iconMime.setImageDrawable(stack.root.loadIcon(context)); 224 225 final Drawable crumb = context.getDrawable(R.drawable.ic_breadcrumb_arrow); 226 crumb.setBounds(0, 0, crumb.getIntrinsicWidth(), crumb.getIntrinsicHeight()); 227 228 final SpannableStringBuilder builder = new SpannableStringBuilder(); 229 builder.append(stack.root.title); 230 for (int i = stack.size() - 2; i >= 0; i--) { 231 appendDrawable(builder, crumb); 232 builder.append(stack.get(i).displayName); 233 } 234 title.setText(builder); 235 title.setEllipsize(TruncateAt.MIDDLE); 236 237 if (line2 != null) line2.setVisibility(View.GONE); 238 239 return convertView; 240 } 241 242 @Override getCount()243 public int getCount() { 244 return mStacks != null ? mStacks.size() : 0; 245 } 246 247 @Override getItem(int position)248 public DocumentStack getItem(int position) { 249 return mStacks.get(position); 250 } 251 252 @Override getItemId(int position)253 public long getItemId(int position) { 254 return getItem(position).hashCode(); 255 } 256 } 257 appendDrawable(SpannableStringBuilder b, Drawable d)258 private static void appendDrawable(SpannableStringBuilder b, Drawable d) { 259 final int length = b.length(); 260 b.append("\u232a"); 261 b.setSpan(new ImageSpan(d), length, b.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 262 } 263 } 264