Home | History | Annotate | Download | only in storage
      1 /*
      2  * Copyright (C) 2015 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.tv.settings.device.storage;
     18 
     19 import android.content.pm.ApplicationInfo;
     20 import android.content.pm.PackageManager;
     21 import android.graphics.drawable.Drawable;
     22 import android.os.AsyncTask;
     23 import android.os.Bundle;
     24 import android.os.storage.StorageManager;
     25 import android.os.storage.VolumeInfo;
     26 import android.support.annotation.NonNull;
     27 import android.support.v17.leanback.app.GuidedStepFragment;
     28 import android.support.v17.leanback.widget.GuidanceStylist;
     29 import android.support.v17.leanback.widget.GuidedAction;
     30 import android.text.TextUtils;
     31 import android.util.ArrayMap;
     32 
     33 import com.android.settingslib.applications.ApplicationsState;
     34 import com.android.tv.settings.R;
     35 import com.android.tv.settings.device.apps.MoveAppActivity;
     36 
     37 import java.util.ArrayList;
     38 import java.util.List;
     39 import java.util.Map;
     40 
     41 public class BackupAppsStepFragment extends GuidedStepFragment implements
     42         ApplicationsState.Callbacks {
     43 
     44     private static final int ACTION_NO_APPS = 0;
     45     private static final int ACTION_MIGRATE_DATA = 1;
     46     private static final int ACTION_BACKUP_APP_BASE = 100;
     47 
     48     private ApplicationsState mApplicationsState;
     49     private ApplicationsState.Session mSession;
     50 
     51     private PackageManager mPackageManager;
     52     private StorageManager mStorageManager;
     53 
     54     private String mVolumeId;
     55     private ApplicationsState.AppFilter mAppFilter;
     56 
     57     private IconLoaderTask mIconLoaderTask;
     58     private final Map<String, Drawable> mIconMap = new ArrayMap<>();
     59 
     60     private final List<ApplicationsState.AppEntry> mEntries = new ArrayList<>();
     61 
     62     public static BackupAppsStepFragment newInstance(String volumeId) {
     63         final BackupAppsStepFragment fragment = new BackupAppsStepFragment();
     64         final Bundle b = new Bundle(1);
     65         b.putString(VolumeInfo.EXTRA_VOLUME_ID, volumeId);
     66         fragment.setArguments(b);
     67         return fragment;
     68     }
     69 
     70     @Override
     71     public void onCreate(Bundle savedInstanceState) {
     72         // Need mPackageManager before onCreateActions, which is called from super.onCreate
     73         mPackageManager = getActivity().getPackageManager();
     74         mStorageManager = getActivity().getSystemService(StorageManager.class);
     75 
     76         mVolumeId = getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID);
     77         final VolumeInfo info = mStorageManager.findVolumeById(mVolumeId);
     78         if (info != null) {
     79             mAppFilter = new ApplicationsState.VolumeFilter(info.getFsUuid());
     80         } else {
     81             if (!getFragmentManager().popBackStackImmediate()) {
     82                 getActivity().finish();
     83             }
     84             mAppFilter = new ApplicationsState.AppFilter() {
     85                 @Override
     86                 public void init() {}
     87 
     88                 @Override
     89                 public boolean filterApp(ApplicationsState.AppEntry info) {
     90                     return false;
     91                 }
     92             };
     93         }
     94 
     95         mApplicationsState = ApplicationsState.getInstance(getActivity().getApplication());
     96         mSession = mApplicationsState.newSession(this);
     97 
     98         super.onCreate(savedInstanceState);
     99     }
    100 
    101     @Override
    102     public void onResume() {
    103         super.onResume();
    104         mSession.onResume();
    105         updateActions();
    106     }
    107 
    108     @Override
    109     public void onPause() {
    110         super.onPause();
    111         mSession.onPause();
    112     }
    113 
    114     @Override
    115     public void onDestroy() {
    116         super.onDestroy();
    117         mSession.onDestroy();
    118     }
    119 
    120     @Override
    121     public @NonNull GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
    122         final String title;
    123         final VolumeInfo volumeInfo = mStorageManager.findVolumeById(mVolumeId);
    124         final String volumeDesc = mStorageManager.getBestVolumeDescription(volumeInfo);
    125         final String primaryStorageVolumeId =
    126                 mPackageManager.getPrimaryStorageCurrentVolume().getId();
    127         if (TextUtils.equals(primaryStorageVolumeId, volumeInfo.getId())) {
    128             title = getString(R.string.storage_wizard_back_up_apps_and_data_title, volumeDesc);
    129         } else {
    130             title = getString(R.string.storage_wizard_back_up_apps_title, volumeDesc);
    131         }
    132         return new GuidanceStylist.Guidance(
    133                 title,
    134                 "",
    135                 "",
    136                 getActivity().getDrawable(R.drawable.ic_storage_132dp));
    137     }
    138 
    139     @Override
    140     public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
    141         final List<ApplicationsState.AppEntry> entries = mSession.rebuild(mAppFilter,
    142                 ApplicationsState.ALPHA_COMPARATOR);
    143         if (entries != null) {
    144             actions.addAll(getAppActions(true, entries));
    145         }
    146     }
    147 
    148     private List<GuidedAction> getAppActions(boolean refreshIcons,
    149             List<ApplicationsState.AppEntry> entries) {
    150 
    151         final List<GuidedAction> actions = new ArrayList<>(entries.size() + 1);
    152 
    153         boolean showMigrate = false;
    154         final VolumeInfo currentExternal = mPackageManager.getPrimaryStorageCurrentVolume();
    155         // currentExternal will be null if the drive is not mounted. Don't offer the option to
    156         // migrate if so.
    157         if (currentExternal != null
    158                 && TextUtils.equals(currentExternal.getId(), mVolumeId)) {
    159             final List<VolumeInfo> candidates =
    160                     mPackageManager.getPrimaryStorageCandidateVolumes();
    161             for (final VolumeInfo candidate : candidates) {
    162                 if (!TextUtils.equals(candidate.getId(), mVolumeId)) {
    163                     showMigrate = true;
    164                     break;
    165                 }
    166             }
    167         }
    168 
    169         if (showMigrate) {
    170             actions.add(new GuidedAction.Builder(getContext())
    171                     .id(ACTION_MIGRATE_DATA)
    172                     .title(R.string.storage_migrate_away)
    173                     .build());
    174         }
    175 
    176         int index = ACTION_BACKUP_APP_BASE;
    177         for (final ApplicationsState.AppEntry entry : entries) {
    178             final ApplicationInfo info = entry.info;
    179             entry.ensureLabel(getActivity());
    180             actions.add(new GuidedAction.Builder(getContext())
    181                     .title(entry.label)
    182                     .description(entry.sizeStr)
    183                     .icon(mIconMap.get(info.packageName))
    184                     .id(index++)
    185                     .build());
    186         }
    187         mEntries.clear();
    188         mEntries.addAll(entries);
    189 
    190         if (refreshIcons) {
    191             if (mIconLoaderTask != null) {
    192                 mIconLoaderTask.cancel(true);
    193             }
    194             mIconLoaderTask = new IconLoaderTask(entries);
    195             mIconLoaderTask.execute();
    196         }
    197 
    198         if (actions.size() == 0) {
    199             actions.add(new GuidedAction.Builder(getContext())
    200                     .id(ACTION_NO_APPS)
    201                     .title(R.string.storage_no_apps)
    202                     .build());
    203         }
    204         return actions;
    205     }
    206 
    207     private void updateActions() {
    208         final List<ApplicationsState.AppEntry> entries = mSession.rebuild(mAppFilter,
    209                 ApplicationsState.ALPHA_COMPARATOR);
    210         if (entries != null) {
    211             setActions(getAppActions(true, entries));
    212         } else {
    213             setActions(getAppActions(true, mEntries));
    214         }
    215     }
    216 
    217     @Override
    218     public void onGuidedActionClicked(GuidedAction action) {
    219         final int actionId = (int) action.getId();
    220         if (actionId == ACTION_MIGRATE_DATA) {
    221             startActivity(MigrateStorageActivity.getLaunchIntent(getActivity(), mVolumeId, false));
    222         } else if (actionId == ACTION_NO_APPS) {
    223             if (!getFragmentManager().popBackStackImmediate()) {
    224                 getActivity().finish();
    225             }
    226         } else if (actionId >= ACTION_BACKUP_APP_BASE
    227                 && actionId < mEntries.size() + ACTION_BACKUP_APP_BASE) {
    228             final ApplicationsState.AppEntry entry =
    229                     mEntries.get(actionId - ACTION_BACKUP_APP_BASE);
    230             entry.ensureLabel(getActivity());
    231             startActivity(MoveAppActivity.getLaunchIntent(getActivity(), entry.info.packageName,
    232                     entry.label));
    233         } else {
    234             throw new IllegalArgumentException("Unknown action " + action);
    235         }
    236     }
    237 
    238     @Override
    239     public void onRunningStateChanged(boolean running) {
    240         updateActions();
    241     }
    242 
    243     @Override
    244     public void onPackageListChanged() {
    245         updateActions();
    246     }
    247 
    248     @Override
    249     public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
    250         setActions(getAppActions(true, apps));
    251     }
    252 
    253     @Override
    254     public void onLauncherInfoChanged() {
    255         updateActions();
    256     }
    257 
    258     @Override
    259     public void onLoadEntriesCompleted() {
    260         updateActions();
    261     }
    262 
    263     @Override
    264     public void onPackageIconChanged() {
    265         updateActions();
    266     }
    267 
    268     @Override
    269     public void onPackageSizeChanged(String packageName) {
    270         updateActions();
    271     }
    272 
    273     @Override
    274     public void onAllSizesComputed() {
    275         updateActions();
    276     }
    277 
    278     private class IconLoaderTask extends AsyncTask<Void, Void, Map<String, Drawable>> {
    279         private final List<ApplicationsState.AppEntry> mEntries;
    280 
    281         public IconLoaderTask(List<ApplicationsState.AppEntry> entries) {
    282             mEntries = entries;
    283         }
    284 
    285         @Override
    286         protected Map<String, Drawable> doInBackground(Void... params) {
    287             // NB: Java doesn't like parameterized generics in varargs
    288             final Map<String, Drawable> result = new ArrayMap<>(mEntries.size());
    289             for (final ApplicationsState.AppEntry entry : mEntries) {
    290                 result.put(entry.info.packageName, mPackageManager.getApplicationIcon(entry.info));
    291             }
    292             return result;
    293         }
    294 
    295         @Override
    296         protected void onPostExecute(Map<String, Drawable> stringDrawableMap) {
    297             mIconLoaderTask = null;
    298             if (!isAdded()) {
    299                 return;
    300             }
    301             mIconMap.putAll(stringDrawableMap);
    302             setActions(getAppActions(false, mEntries));
    303         }
    304     }
    305 
    306 }
    307