Home | History | Annotate | Download | only in documentsui
      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.android.internal.util.Objects;
     50 import com.google.common.collect.Lists;
     51 
     52 import java.util.Collection;
     53 import java.util.Collections;
     54 import java.util.Comparator;
     55 import java.util.List;
     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 
     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 
     81     public static RootsFragment get(FragmentManager fm) {
     82         return (RootsFragment) fm.findFragmentById(R.id.container_roots);
     83     }
     84 
     85     @Override
     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
     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
    134     public void onResume() {
    135         super.onResume();
    136 
    137         final Context context = getActivity();
    138         final State state = ((DocumentsActivity) context).getDisplayState();
    139         state.showAdvanced = state.forceAdvanced
    140                 | SettingsActivity.getDisplayAdvancedDevices(context);
    141 
    142         if (state.action == ACTION_GET_CONTENT) {
    143             mList.setOnItemLongClickListener(mItemLongClickListener);
    144         } else {
    145             mList.setOnItemLongClickListener(null);
    146             mList.setLongClickable(false);
    147         }
    148 
    149         getLoaderManager().restartLoader(2, null, mCallbacks);
    150     }
    151 
    152     public void onCurrentRootChanged() {
    153         if (mAdapter == null) return;
    154 
    155         final RootInfo root = ((DocumentsActivity) getActivity()).getCurrentRoot();
    156         for (int i = 0; i < mAdapter.getCount(); i++) {
    157             final Object item = mAdapter.getItem(i);
    158             if (item instanceof RootItem) {
    159                 final RootInfo testRoot = ((RootItem) item).root;
    160                 if (Objects.equal(testRoot, root)) {
    161                     mList.setItemChecked(i, true);
    162                     return;
    163                 }
    164             }
    165         }
    166     }
    167 
    168     private void showAppDetails(ResolveInfo ri) {
    169         final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
    170         intent.setData(Uri.fromParts("package", ri.activityInfo.packageName, null));
    171         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
    172         startActivity(intent);
    173     }
    174 
    175     private OnItemClickListener mItemListener = new OnItemClickListener() {
    176         @Override
    177         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    178             final DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this);
    179             final Item item = mAdapter.getItem(position);
    180             if (item instanceof RootItem) {
    181                 activity.onRootPicked(((RootItem) item).root, true);
    182             } else if (item instanceof AppItem) {
    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 
    206         public Item(int layoutId) {
    207             mLayoutId = layoutId;
    208         }
    209 
    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 
    219         public abstract void bindView(View convertView);
    220     }
    221 
    222     private static class RootItem extends Item {
    223         public final RootInfo root;
    224 
    225         public RootItem(RootInfo root) {
    226             super(R.layout.item_root);
    227             this.root = root;
    228         }
    229 
    230         @Override
    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.loadIcon(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 {
    253         public SpacerItem() {
    254             super(R.layout.item_root_spacer);
    255         }
    256 
    257         @Override
    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 
    266         public AppItem(ResolveInfo info) {
    267             super(R.layout.item_root);
    268             this.info = info;
    269         }
    270 
    271         @Override
    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> {
    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
    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
    366         public boolean areAllItemsEnabled() {
    367             return false;
    368         }
    369 
    370         @Override
    371         public boolean isEnabled(int position) {
    372             return getItemViewType(position) != 1;
    373         }
    374 
    375         @Override
    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
    386         public int getViewTypeCount() {
    387             return 2;
    388         }
    389     }
    390 
    391     public static class RootComparator implements Comparator<RootInfo> {
    392         @Override
    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