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 17package com.android.documentsui; 18 19import android.app.Fragment; 20import android.app.FragmentManager; 21import android.app.FragmentTransaction; 22import android.app.LoaderManager.LoaderCallbacks; 23import android.content.Context; 24import android.content.Intent; 25import android.content.Loader; 26import android.content.pm.PackageManager; 27import android.content.pm.ResolveInfo; 28import android.os.Bundle; 29import android.text.TextUtils; 30import android.text.format.Formatter; 31import android.view.LayoutInflater; 32import android.view.View; 33import android.view.ViewGroup; 34import android.widget.AdapterView; 35import android.widget.AdapterView.OnItemClickListener; 36import android.widget.ArrayAdapter; 37import android.widget.ImageView; 38import android.widget.ListView; 39import android.widget.TextView; 40 41import com.android.documentsui.DocumentsActivity.State; 42import com.android.documentsui.model.DocumentInfo; 43import com.android.documentsui.model.RootInfo; 44import com.android.internal.util.Objects; 45import com.google.common.collect.Lists; 46 47import java.util.Collection; 48import java.util.Collections; 49import java.util.Comparator; 50import java.util.List; 51 52/** 53 * Display list of known storage backend roots. 54 */ 55public class RootsFragment extends Fragment { 56 57 private ListView mList; 58 private RootsAdapter mAdapter; 59 60 private LoaderCallbacks<Collection<RootInfo>> mCallbacks; 61 62 private static final String EXTRA_INCLUDE_APPS = "includeApps"; 63 64 public static void show(FragmentManager fm, Intent includeApps) { 65 final Bundle args = new Bundle(); 66 args.putParcelable(EXTRA_INCLUDE_APPS, includeApps); 67 68 final RootsFragment fragment = new RootsFragment(); 69 fragment.setArguments(args); 70 71 final FragmentTransaction ft = fm.beginTransaction(); 72 ft.replace(R.id.container_roots, fragment); 73 ft.commitAllowingStateLoss(); 74 } 75 76 public static RootsFragment get(FragmentManager fm) { 77 return (RootsFragment) fm.findFragmentById(R.id.container_roots); 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_roots, container, false); 86 mList = (ListView) view.findViewById(android.R.id.list); 87 mList.setOnItemClickListener(mItemListener); 88 mList.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 89 90 return view; 91 } 92 93 @Override 94 public void onActivityCreated(Bundle savedInstanceState) { 95 super.onActivityCreated(savedInstanceState); 96 97 final Context context = getActivity(); 98 final RootsCache roots = DocumentsApplication.getRootsCache(context); 99 final State state = ((DocumentsActivity) context).getDisplayState(); 100 101 mCallbacks = new LoaderCallbacks<Collection<RootInfo>>() { 102 @Override 103 public Loader<Collection<RootInfo>> onCreateLoader(int id, Bundle args) { 104 return new RootsLoader(context, roots, state); 105 } 106 107 @Override 108 public void onLoadFinished( 109 Loader<Collection<RootInfo>> loader, Collection<RootInfo> result) { 110 if (!isAdded()) return; 111 112 final Intent includeApps = getArguments().getParcelable(EXTRA_INCLUDE_APPS); 113 114 mAdapter = new RootsAdapter(context, result, includeApps); 115 mList.setAdapter(mAdapter); 116 117 onCurrentRootChanged(); 118 } 119 120 @Override 121 public void onLoaderReset(Loader<Collection<RootInfo>> loader) { 122 mAdapter = null; 123 mList.setAdapter(null); 124 } 125 }; 126 } 127 128 @Override 129 public void onResume() { 130 super.onResume(); 131 132 final Context context = getActivity(); 133 final State state = ((DocumentsActivity) context).getDisplayState(); 134 state.showAdvanced = SettingsActivity.getDisplayAdvancedDevices(context); 135 136 getLoaderManager().restartLoader(2, null, mCallbacks); 137 } 138 139 public void onCurrentRootChanged() { 140 if (mAdapter == null) return; 141 142 final RootInfo root = ((DocumentsActivity) getActivity()).getCurrentRoot(); 143 for (int i = 0; i < mAdapter.getCount(); i++) { 144 final Object item = mAdapter.getItem(i); 145 if (item instanceof RootItem) { 146 final RootInfo testRoot = ((RootItem) item).root; 147 if (Objects.equal(testRoot, root)) { 148 mList.setItemChecked(i, true); 149 return; 150 } 151 } 152 } 153 } 154 155 private OnItemClickListener mItemListener = new OnItemClickListener() { 156 @Override 157 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 158 final DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this); 159 final Item item = mAdapter.getItem(position); 160 if (item instanceof RootItem) { 161 activity.onRootPicked(((RootItem) item).root, true); 162 } else if (item instanceof AppItem) { 163 activity.onAppPicked(((AppItem) item).info); 164 } else { 165 throw new IllegalStateException("Unknown root: " + item); 166 } 167 } 168 }; 169 170 private static abstract class Item { 171 private final int mLayoutId; 172 173 public Item(int layoutId) { 174 mLayoutId = layoutId; 175 } 176 177 public View getView(View convertView, ViewGroup parent) { 178 if (convertView == null) { 179 convertView = LayoutInflater.from(parent.getContext()) 180 .inflate(mLayoutId, parent, false); 181 } 182 bindView(convertView); 183 return convertView; 184 } 185 186 public abstract void bindView(View convertView); 187 } 188 189 private static class RootItem extends Item { 190 public final RootInfo root; 191 192 public RootItem(RootInfo root) { 193 super(R.layout.item_root); 194 this.root = root; 195 } 196 197 @Override 198 public void bindView(View convertView) { 199 final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon); 200 final TextView title = (TextView) convertView.findViewById(android.R.id.title); 201 final TextView summary = (TextView) convertView.findViewById(android.R.id.summary); 202 203 final Context context = convertView.getContext(); 204 icon.setImageDrawable(root.loadIcon(context)); 205 title.setText(root.title); 206 207 // Show available space if no summary 208 String summaryText = root.summary; 209 if (TextUtils.isEmpty(summaryText) && root.availableBytes >= 0) { 210 summaryText = context.getString(R.string.root_available_bytes, 211 Formatter.formatFileSize(context, root.availableBytes)); 212 } 213 214 summary.setText(summaryText); 215 summary.setVisibility(TextUtils.isEmpty(summaryText) ? View.GONE : View.VISIBLE); 216 } 217 } 218 219 private static class SpacerItem extends Item { 220 public SpacerItem() { 221 super(R.layout.item_root_spacer); 222 } 223 224 @Override 225 public void bindView(View convertView) { 226 // Nothing to bind 227 } 228 } 229 230 private static class AppItem extends Item { 231 public final ResolveInfo info; 232 233 public AppItem(ResolveInfo info) { 234 super(R.layout.item_root); 235 this.info = info; 236 } 237 238 @Override 239 public void bindView(View convertView) { 240 final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon); 241 final TextView title = (TextView) convertView.findViewById(android.R.id.title); 242 final TextView summary = (TextView) convertView.findViewById(android.R.id.summary); 243 244 final PackageManager pm = convertView.getContext().getPackageManager(); 245 icon.setImageDrawable(info.loadIcon(pm)); 246 title.setText(info.loadLabel(pm)); 247 248 // TODO: match existing summary behavior from disambig dialog 249 summary.setVisibility(View.GONE); 250 } 251 } 252 253 private static class RootsAdapter extends ArrayAdapter<Item> { 254 public RootsAdapter(Context context, Collection<RootInfo> roots, Intent includeApps) { 255 super(context, 0); 256 257 RootItem recents = null; 258 RootItem images = null; 259 RootItem videos = null; 260 RootItem audio = null; 261 RootItem downloads = null; 262 263 final List<RootInfo> clouds = Lists.newArrayList(); 264 final List<RootInfo> locals = Lists.newArrayList(); 265 266 for (RootInfo root : roots) { 267 if (root.isRecents()) { 268 recents = new RootItem(root); 269 } else if (root.isExternalStorage()) { 270 locals.add(root); 271 } else if (root.isDownloads()) { 272 downloads = new RootItem(root); 273 } else if (root.isImages()) { 274 images = new RootItem(root); 275 } else if (root.isVideos()) { 276 videos = new RootItem(root); 277 } else if (root.isAudio()) { 278 audio = new RootItem(root); 279 } else { 280 clouds.add(root); 281 } 282 } 283 284 final RootComparator comp = new RootComparator(); 285 Collections.sort(clouds, comp); 286 Collections.sort(locals, comp); 287 288 if (recents != null) add(recents); 289 290 for (RootInfo cloud : clouds) { 291 add(new RootItem(cloud)); 292 } 293 294 if (images != null) add(images); 295 if (videos != null) add(videos); 296 if (audio != null) add(audio); 297 if (downloads != null) add(downloads); 298 299 for (RootInfo local : locals) { 300 add(new RootItem(local)); 301 } 302 303 if (includeApps != null) { 304 final PackageManager pm = context.getPackageManager(); 305 final List<ResolveInfo> infos = pm.queryIntentActivities( 306 includeApps, PackageManager.MATCH_DEFAULT_ONLY); 307 308 final List<AppItem> apps = Lists.newArrayList(); 309 310 // Omit ourselves from the list 311 for (ResolveInfo info : infos) { 312 if (!context.getPackageName().equals(info.activityInfo.packageName)) { 313 apps.add(new AppItem(info)); 314 } 315 } 316 317 if (apps.size() > 0) { 318 add(new SpacerItem()); 319 for (Item item : apps) { 320 add(item); 321 } 322 } 323 } 324 } 325 326 @Override 327 public View getView(int position, View convertView, ViewGroup parent) { 328 final Item item = getItem(position); 329 return item.getView(convertView, parent); 330 } 331 332 @Override 333 public boolean areAllItemsEnabled() { 334 return false; 335 } 336 337 @Override 338 public boolean isEnabled(int position) { 339 return getItemViewType(position) != 1; 340 } 341 342 @Override 343 public int getItemViewType(int position) { 344 final Item item = getItem(position); 345 if (item instanceof RootItem || item instanceof AppItem) { 346 return 0; 347 } else { 348 return 1; 349 } 350 } 351 352 @Override 353 public int getViewTypeCount() { 354 return 2; 355 } 356 } 357 358 public static class RootComparator implements Comparator<RootInfo> { 359 @Override 360 public int compare(RootInfo lhs, RootInfo rhs) { 361 final int score = DocumentInfo.compareToIgnoreCaseNullable(lhs.title, rhs.title); 362 if (score != 0) { 363 return score; 364 } else { 365 return DocumentInfo.compareToIgnoreCaseNullable(lhs.summary, rhs.summary); 366 } 367 } 368 } 369} 370