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