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.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 
     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         onDisplayStateChanged();
    137     }
    138 
    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 
    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 
    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 
    207         public Item(int layoutId) {
    208             mLayoutId = layoutId;
    209         }
    210 
    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 
    220         public abstract void bindView(View convertView);
    221     }
    222 
    223     private static class RootItem extends Item {
    224         public final RootInfo root;
    225 
    226         public RootItem(RootInfo root) {
    227             super(R.layout.item_root);
    228             this.root = root;
    229         }
    230 
    231         @Override
    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 {
    254         public SpacerItem() {
    255             super(R.layout.item_root_spacer);
    256         }
    257 
    258         @Override
    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 
    267         public AppItem(ResolveInfo info) {
    268             super(R.layout.item_root);
    269             this.info = info;
    270         }
    271 
    272         @Override
    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> {
    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
    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
    367         public boolean areAllItemsEnabled() {
    368             return false;
    369         }
    370 
    371         @Override
    372         public boolean isEnabled(int position) {
    373             return getItemViewType(position) != 1;
    374         }
    375 
    376         @Override
    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
    387         public int getViewTypeCount() {
    388             return 2;
    389         }
    390     }
    391 
    392     public static class RootComparator implements Comparator<RootInfo> {
    393         @Override
    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