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