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