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