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