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.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 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 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 = ((DocumentsActivity) 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 ((DocumentsActivity) context).setRootsDrawerOpen(true); 114 } 115 } 116 117 @Override 118 public void onLoaderReset(Loader<List<DocumentStack>> loader) { 119 mAdapter.swapStacks(null); 120 } 121 }; 122 123 return view; 124 } 125 126 @Override 127 public void onStart() { 128 super.onStart(); 129 getLoaderManager().restartLoader(LOADER_RECENTS, getArguments(), mCallbacks); 130 } 131 132 @Override 133 public void onStop() { 134 super.onStop(); 135 getLoaderManager().destroyLoader(LOADER_RECENTS); 136 } 137 138 private OnItemClickListener mItemListener = new OnItemClickListener() { 139 @Override 140 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 141 final DocumentStack stack = mAdapter.getItem(position); 142 ((DocumentsActivity) getActivity()).onStackPicked(stack); 143 } 144 }; 145 146 public static class RecentsCreateLoader extends UriDerivativeLoader<Uri, List<DocumentStack>> { 147 private final RootsCache mRoots; 148 private final State mState; 149 150 public RecentsCreateLoader(Context context, RootsCache roots, State state) { 151 super(context, RecentsProvider.buildRecent()); 152 mRoots = roots; 153 mState = state; 154 } 155 156 @Override 157 public List<DocumentStack> loadInBackground(Uri uri, CancellationSignal signal) { 158 final Collection<RootInfo> matchingRoots = mRoots.getMatchingRootsBlocking(mState); 159 final ArrayList<DocumentStack> result = Lists.newArrayList(); 160 161 final ContentResolver resolver = getContext().getContentResolver(); 162 final Cursor cursor = resolver.query( 163 uri, null, null, null, RecentColumns.TIMESTAMP + " DESC", signal); 164 try { 165 while (cursor != null && cursor.moveToNext()) { 166 final byte[] rawStack = cursor.getBlob( 167 cursor.getColumnIndex(RecentColumns.STACK)); 168 try { 169 final DocumentStack stack = new DocumentStack(); 170 DurableUtils.readFromArray(rawStack, stack); 171 172 // Only update root here to avoid spinning up all 173 // providers; we update the stack during the actual 174 // restore. This also filters away roots that don't 175 // match current filter. 176 stack.updateRoot(matchingRoots); 177 result.add(stack); 178 } catch (IOException e) { 179 Log.w(TAG, "Failed to resolve stack: " + e); 180 } 181 } 182 } finally { 183 IoUtils.closeQuietly(cursor); 184 } 185 186 return result; 187 } 188 } 189 190 private class DocumentStackAdapter extends BaseAdapter { 191 private List<DocumentStack> mStacks; 192 193 public DocumentStackAdapter() { 194 } 195 196 public void swapStacks(List<DocumentStack> stacks) { 197 mStacks = stacks; 198 199 if (isEmpty()) { 200 mEmptyView.setVisibility(View.VISIBLE); 201 } else { 202 mEmptyView.setVisibility(View.GONE); 203 } 204 205 notifyDataSetChanged(); 206 } 207 208 @Override 209 public View getView(int position, View convertView, ViewGroup parent) { 210 final Context context = parent.getContext(); 211 212 if (convertView == null) { 213 final LayoutInflater inflater = LayoutInflater.from(context); 214 convertView = inflater.inflate(R.layout.item_doc_list, parent, false); 215 } 216 217 final ImageView iconMime = (ImageView) convertView.findViewById(R.id.icon_mime); 218 final TextView title = (TextView) convertView.findViewById(android.R.id.title); 219 final View line2 = convertView.findViewById(R.id.line2); 220 221 final DocumentStack stack = getItem(position); 222 iconMime.setImageDrawable(stack.root.loadIcon(context)); 223 224 final Drawable crumb = context.getDrawable(R.drawable.ic_breadcrumb_arrow); 225 crumb.setBounds(0, 0, crumb.getIntrinsicWidth(), crumb.getIntrinsicHeight()); 226 227 final SpannableStringBuilder builder = new SpannableStringBuilder(); 228 builder.append(stack.root.title); 229 for (int i = stack.size() - 2; i >= 0; i--) { 230 appendDrawable(builder, crumb); 231 builder.append(stack.get(i).displayName); 232 } 233 title.setText(builder); 234 title.setEllipsize(TruncateAt.MIDDLE); 235 236 if (line2 != null) line2.setVisibility(View.GONE); 237 238 return convertView; 239 } 240 241 @Override 242 public int getCount() { 243 return mStacks != null ? mStacks.size() : 0; 244 } 245 246 @Override 247 public DocumentStack getItem(int position) { 248 return mStacks.get(position); 249 } 250 251 @Override 252 public long getItemId(int position) { 253 return getItem(position).hashCode(); 254 } 255 } 256 257 private static void appendDrawable(SpannableStringBuilder b, Drawable d) { 258 final int length = b.length(); 259 b.append("\u232a"); 260 b.setSpan(new ImageSpan(d), length, b.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 261 } 262 } 263