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