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.text.TextUtils; 27 import android.util.ArrayMap; 28 29 import androidx.annotation.NonNull; 30 import androidx.leanback.app.GuidedStepFragment; 31 import androidx.leanback.widget.GuidanceStylist; 32 import androidx.leanback.widget.GuidedAction; 33 34 import com.android.settingslib.applications.ApplicationsState; 35 import com.android.tv.settings.R; 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 newInstance(String volumeId)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 onCreate(Bundle savedInstanceState)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 onResume()103 public void onResume() { 104 super.onResume(); 105 mSession.onResume(); 106 updateActions(); 107 } 108 109 @Override onPause()110 public void onPause() { 111 super.onPause(); 112 mSession.onPause(); 113 } 114 115 @Override onDestroy()116 public void onDestroy() { 117 super.onDestroy(); 118 mSession.onDestroy(); 119 } 120 121 @Override onCreateGuidance(Bundle savedInstanceState)122 public @NonNull GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) { 123 final String title; 124 final VolumeInfo volumeInfo = mStorageManager.findVolumeById(mVolumeId); 125 final String volumeDesc = mStorageManager.getBestVolumeDescription(volumeInfo); 126 final String primaryStorageVolumeId = 127 mPackageManager.getPrimaryStorageCurrentVolume().getId(); 128 if (TextUtils.equals(primaryStorageVolumeId, volumeInfo.getId())) { 129 title = getString(R.string.storage_wizard_back_up_apps_and_data_title, volumeDesc); 130 } else { 131 title = getString(R.string.storage_wizard_back_up_apps_title, volumeDesc); 132 } 133 return new GuidanceStylist.Guidance( 134 title, 135 "", 136 "", 137 getActivity().getDrawable(R.drawable.ic_storage_132dp)); 138 } 139 140 @Override onCreateActions(@onNull List<GuidedAction> actions, Bundle savedInstanceState)141 public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) { 142 final List<ApplicationsState.AppEntry> entries = mSession.rebuild(mAppFilter, 143 ApplicationsState.ALPHA_COMPARATOR); 144 if (entries != null) { 145 actions.addAll(getAppActions(true, entries)); 146 } 147 } 148 getAppActions(boolean refreshIcons, List<ApplicationsState.AppEntry> entries)149 private List<GuidedAction> getAppActions(boolean refreshIcons, 150 List<ApplicationsState.AppEntry> entries) { 151 152 final List<GuidedAction> actions = new ArrayList<>(entries.size() + 1); 153 154 boolean showMigrate = false; 155 final VolumeInfo currentExternal = mPackageManager.getPrimaryStorageCurrentVolume(); 156 // currentExternal will be null if the drive is not mounted. Don't offer the option to 157 // migrate if so. 158 if (currentExternal != null 159 && TextUtils.equals(currentExternal.getId(), mVolumeId)) { 160 final List<VolumeInfo> candidates = 161 mPackageManager.getPrimaryStorageCandidateVolumes(); 162 for (final VolumeInfo candidate : candidates) { 163 if (!TextUtils.equals(candidate.getId(), mVolumeId)) { 164 showMigrate = true; 165 break; 166 } 167 } 168 } 169 170 if (showMigrate) { 171 actions.add(new GuidedAction.Builder(getContext()) 172 .id(ACTION_MIGRATE_DATA) 173 .title(R.string.storage_migrate_away) 174 .build()); 175 } 176 177 int index = ACTION_BACKUP_APP_BASE; 178 for (final ApplicationsState.AppEntry entry : entries) { 179 final ApplicationInfo info = entry.info; 180 entry.ensureLabel(getActivity()); 181 actions.add(new GuidedAction.Builder(getContext()) 182 .title(entry.label) 183 .description(entry.sizeStr) 184 .icon(mIconMap.get(info.packageName)) 185 .id(index++) 186 .build()); 187 } 188 mEntries.clear(); 189 mEntries.addAll(entries); 190 191 if (refreshIcons) { 192 if (mIconLoaderTask != null) { 193 mIconLoaderTask.cancel(true); 194 } 195 mIconLoaderTask = new IconLoaderTask(entries); 196 mIconLoaderTask.execute(); 197 } 198 199 if (actions.size() == 0) { 200 actions.add(new GuidedAction.Builder(getContext()) 201 .id(ACTION_NO_APPS) 202 .title(R.string.storage_no_apps) 203 .build()); 204 } 205 return actions; 206 } 207 updateActions()208 private void updateActions() { 209 final List<ApplicationsState.AppEntry> entries = mSession.rebuild(mAppFilter, 210 ApplicationsState.ALPHA_COMPARATOR); 211 if (entries != null) { 212 setActions(getAppActions(true, entries)); 213 } else { 214 setActions(getAppActions(true, mEntries)); 215 } 216 } 217 218 @Override onGuidedActionClicked(GuidedAction action)219 public void onGuidedActionClicked(GuidedAction action) { 220 final int actionId = (int) action.getId(); 221 if (actionId == ACTION_MIGRATE_DATA) { 222 startActivity(MigrateStorageActivity.getLaunchIntent(getActivity(), mVolumeId, false)); 223 } else if (actionId == ACTION_NO_APPS) { 224 if (!getFragmentManager().popBackStackImmediate()) { 225 getActivity().finish(); 226 } 227 } else if (actionId >= ACTION_BACKUP_APP_BASE 228 && actionId < mEntries.size() + ACTION_BACKUP_APP_BASE) { 229 final ApplicationsState.AppEntry entry = 230 mEntries.get(actionId - ACTION_BACKUP_APP_BASE); 231 entry.ensureLabel(getActivity()); 232 startActivity(MoveAppActivity.getLaunchIntent(getActivity(), entry.info.packageName, 233 entry.label)); 234 } else { 235 throw new IllegalArgumentException("Unknown action " + action); 236 } 237 } 238 239 @Override onRunningStateChanged(boolean running)240 public void onRunningStateChanged(boolean running) { 241 updateActions(); 242 } 243 244 @Override onPackageListChanged()245 public void onPackageListChanged() { 246 updateActions(); 247 } 248 249 @Override onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps)250 public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) { 251 setActions(getAppActions(true, apps)); 252 } 253 254 @Override onLauncherInfoChanged()255 public void onLauncherInfoChanged() { 256 updateActions(); 257 } 258 259 @Override onLoadEntriesCompleted()260 public void onLoadEntriesCompleted() { 261 updateActions(); 262 } 263 264 @Override onPackageIconChanged()265 public void onPackageIconChanged() { 266 updateActions(); 267 } 268 269 @Override onPackageSizeChanged(String packageName)270 public void onPackageSizeChanged(String packageName) { 271 updateActions(); 272 } 273 274 @Override onAllSizesComputed()275 public void onAllSizesComputed() { 276 updateActions(); 277 } 278 279 private class IconLoaderTask extends AsyncTask<Void, Void, Map<String, Drawable>> { 280 private final List<ApplicationsState.AppEntry> mEntries; 281 IconLoaderTask(List<ApplicationsState.AppEntry> entries)282 public IconLoaderTask(List<ApplicationsState.AppEntry> entries) { 283 mEntries = entries; 284 } 285 286 @Override doInBackground(Void... params)287 protected Map<String, Drawable> doInBackground(Void... params) { 288 // NB: Java doesn't like parameterized generics in varargs 289 final Map<String, Drawable> result = new ArrayMap<>(mEntries.size()); 290 for (final ApplicationsState.AppEntry entry : mEntries) { 291 result.put(entry.info.packageName, mPackageManager.getApplicationIcon(entry.info)); 292 } 293 return result; 294 } 295 296 @Override onPostExecute(Map<String, Drawable> stringDrawableMap)297 protected void onPostExecute(Map<String, Drawable> stringDrawableMap) { 298 mIconLoaderTask = null; 299 if (!isAdded()) { 300 return; 301 } 302 mIconMap.putAll(stringDrawableMap); 303 setActions(getAppActions(false, mEntries)); 304 } 305 } 306 307 } 308