1 /* 2 * Copyright (C) 2016 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.settings.deviceinfo.storage; 18 19 import android.app.Fragment; 20 import android.content.ActivityNotFoundException; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.pm.PackageManager; 24 import android.content.res.TypedArray; 25 import android.graphics.drawable.Drawable; 26 import android.net.TrafficStats; 27 import android.os.Bundle; 28 import android.os.UserHandle; 29 import android.os.storage.VolumeInfo; 30 import android.support.annotation.VisibleForTesting; 31 import android.support.v7.preference.Preference; 32 import android.support.v7.preference.PreferenceScreen; 33 import android.util.Log; 34 import android.util.SparseArray; 35 36 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 37 import com.android.settings.R; 38 import com.android.settings.Settings; 39 import com.android.settings.Utils; 40 import com.android.settings.applications.ManageApplications; 41 import com.android.settings.core.PreferenceController; 42 import com.android.settings.core.instrumentation.MetricsFeatureProvider; 43 import com.android.settings.deviceinfo.PrivateVolumeSettings.SystemInfoFragment; 44 import com.android.settings.deviceinfo.StorageItemPreference; 45 import com.android.settings.overlay.FeatureFactory; 46 import com.android.settingslib.deviceinfo.StorageMeasurement; 47 import com.android.settingslib.deviceinfo.StorageVolumeProvider; 48 49 import java.util.ArrayList; 50 import java.util.List; 51 import java.util.Map; 52 53 /** 54 * StorageItemPreferenceController handles the storage line items which summarize the storage 55 * categorization breakdown. 56 */ 57 public class StorageItemPreferenceController extends PreferenceController { 58 private static final String TAG = "StorageItemPreference"; 59 60 private static final String IMAGE_MIME_TYPE = "image/*"; 61 private static final String SYSTEM_FRAGMENT_TAG = "SystemInfo"; 62 63 @VisibleForTesting 64 static final String PHOTO_KEY = "pref_photos_videos"; 65 @VisibleForTesting 66 static final String AUDIO_KEY = "pref_music_audio"; 67 @VisibleForTesting 68 static final String GAME_KEY = "pref_games"; 69 @VisibleForTesting 70 static final String MOVIES_KEY = "pref_movies"; 71 @VisibleForTesting 72 static final String OTHER_APPS_KEY = "pref_other_apps"; 73 @VisibleForTesting 74 static final String SYSTEM_KEY = "pref_system"; 75 @VisibleForTesting 76 static final String FILES_KEY = "pref_files"; 77 78 private final Fragment mFragment; 79 private final MetricsFeatureProvider mMetricsFeatureProvider; 80 private final StorageVolumeProvider mSvp; 81 private VolumeInfo mVolume; 82 private int mUserId; 83 private long mUsedBytes; 84 private long mTotalSize; 85 86 private PreferenceScreen mScreen; 87 private StorageItemPreference mPhotoPreference; 88 private StorageItemPreference mAudioPreference; 89 private StorageItemPreference mGamePreference; 90 private StorageItemPreference mMoviesPreference; 91 private StorageItemPreference mAppPreference; 92 private StorageItemPreference mFilePreference; 93 private StorageItemPreference mSystemPreference; 94 95 private static final String AUTHORITY_MEDIA = "com.android.providers.media.documents"; 96 97 public StorageItemPreferenceController( 98 Context context, Fragment hostFragment, VolumeInfo volume, StorageVolumeProvider svp) { 99 super(context); 100 mFragment = hostFragment; 101 mVolume = volume; 102 mSvp = svp; 103 mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); 104 mUserId = UserHandle.myUserId(); 105 } 106 107 @Override 108 public boolean isAvailable() { 109 return true; 110 } 111 112 @Override 113 public boolean handlePreferenceTreeClick(Preference preference) { 114 if (preference == null) { 115 return false; 116 } 117 118 Intent intent = null; 119 if (preference.getKey() == null) { 120 return false; 121 } 122 switch (preference.getKey()) { 123 case PHOTO_KEY: 124 intent = getPhotosIntent(); 125 break; 126 case AUDIO_KEY: 127 intent = getAudioIntent(); 128 break; 129 case GAME_KEY: 130 intent = getGamesIntent(); 131 break; 132 case MOVIES_KEY: 133 intent = getMoviesIntent(); 134 break; 135 case OTHER_APPS_KEY: 136 // Because we are likely constructed with a null volume, this is theoretically 137 // possible. 138 if (mVolume == null) { 139 break; 140 } 141 intent = getAppsIntent(); 142 break; 143 case FILES_KEY: 144 intent = getFilesIntent(); 145 FeatureFactory.getFactory(mContext).getMetricsFeatureProvider().action( 146 mContext, MetricsEvent.STORAGE_FILES); 147 break; 148 case SYSTEM_KEY: 149 final SystemInfoFragment dialog = new SystemInfoFragment(); 150 dialog.setTargetFragment(mFragment, 0); 151 dialog.show(mFragment.getFragmentManager(), SYSTEM_FRAGMENT_TAG); 152 return true; 153 } 154 155 if (intent != null) { 156 intent.putExtra(Intent.EXTRA_USER_ID, mUserId); 157 158 launchIntent(intent); 159 return true; 160 } 161 162 return super.handlePreferenceTreeClick(preference); 163 } 164 165 @Override 166 public String getPreferenceKey() { 167 return null; 168 } 169 170 /** 171 * Sets the storage volume to use for when handling taps. 172 */ 173 public void setVolume(VolumeInfo volume) { 174 mVolume = volume; 175 setFilesPreferenceVisibility(); 176 } 177 178 private void setFilesPreferenceVisibility() { 179 if (mScreen != null) { 180 final VolumeInfo sharedVolume = mSvp.findEmulatedForPrivate(mVolume); 181 // If we don't have a shared volume for our internal storage (or the shared volume isn't 182 // mounted as readable for whatever reason), we should hide the File preference. 183 final boolean hideFilePreference = 184 (sharedVolume == null) || !sharedVolume.isMountedReadable(); 185 if (hideFilePreference) { 186 mScreen.removePreference(mFilePreference); 187 } else { 188 mScreen.addPreference(mFilePreference); 189 } 190 } 191 } 192 193 /** 194 * Sets the user id for which this preference controller is handling. 195 */ 196 public void setUserId(UserHandle userHandle) { 197 mUserId = userHandle.getIdentifier(); 198 199 PackageManager pm = mContext.getPackageManager(); 200 badgePreference(pm, userHandle, mPhotoPreference); 201 badgePreference(pm, userHandle, mMoviesPreference); 202 badgePreference(pm, userHandle, mAudioPreference); 203 badgePreference(pm, userHandle, mGamePreference); 204 badgePreference(pm, userHandle, mAppPreference); 205 badgePreference(pm, userHandle, mSystemPreference); 206 badgePreference(pm, userHandle, mFilePreference); 207 } 208 209 private void badgePreference(PackageManager pm, UserHandle userHandle, Preference preference) { 210 if (preference != null) { 211 Drawable currentIcon = preference.getIcon(); 212 // Sigh... Applying the badge to the icon clobbers the tint on the base drawable. 213 // For some reason, re-applying it here means the tint remains. 214 currentIcon = applyTint(mContext, currentIcon); 215 preference.setIcon(pm.getUserBadgedIcon(currentIcon, userHandle)); 216 } 217 } 218 219 private static Drawable applyTint(Context context, Drawable icon) { 220 TypedArray array = 221 context.obtainStyledAttributes(new int[]{android.R.attr.colorControlNormal}); 222 icon = icon.mutate(); 223 icon.setTint(array.getColor(0, 0)); 224 array.recycle(); 225 return icon; 226 } 227 228 @Override 229 public void displayPreference(PreferenceScreen screen) { 230 mScreen = screen; 231 mPhotoPreference = (StorageItemPreference) screen.findPreference(PHOTO_KEY); 232 mAudioPreference = (StorageItemPreference) screen.findPreference(AUDIO_KEY); 233 mGamePreference = (StorageItemPreference) screen.findPreference(GAME_KEY); 234 mMoviesPreference = (StorageItemPreference) screen.findPreference(MOVIES_KEY); 235 mAppPreference = (StorageItemPreference) screen.findPreference(OTHER_APPS_KEY); 236 mSystemPreference = (StorageItemPreference) screen.findPreference(SYSTEM_KEY); 237 mFilePreference = (StorageItemPreference) screen.findPreference(FILES_KEY); 238 239 setFilesPreferenceVisibility(); 240 } 241 242 public void onLoadFinished(SparseArray<StorageAsyncLoader.AppsStorageResult> result, 243 int userId) { 244 final StorageAsyncLoader.AppsStorageResult data = result.get(userId); 245 246 // TODO(b/35927909): Figure out how to split out apps which are only installed for work 247 // profiles in order to attribute those app's code bytes only to that profile. 248 mPhotoPreference.setStorageSize( 249 data.externalStats.imageBytes + data.externalStats.videoBytes, mTotalSize); 250 mAudioPreference.setStorageSize( 251 data.musicAppsSize + data.externalStats.audioBytes, mTotalSize); 252 mGamePreference.setStorageSize(data.gamesSize, mTotalSize); 253 mMoviesPreference.setStorageSize(data.videoAppsSize, mTotalSize); 254 mAppPreference.setStorageSize(data.otherAppsSize, mTotalSize); 255 256 long otherExternalBytes = 257 data.externalStats.totalBytes 258 - data.externalStats.audioBytes 259 - data.externalStats.videoBytes 260 - data.externalStats.imageBytes 261 - data.externalStats.appBytes; 262 mFilePreference.setStorageSize(otherExternalBytes, mTotalSize); 263 264 if (mSystemPreference != null) { 265 // Everything else that hasn't already been attributed is tracked as 266 // belonging to system. 267 long attributedSize = 0; 268 for (int i = 0; i < result.size(); i++) { 269 final StorageAsyncLoader.AppsStorageResult otherData = result.valueAt(i); 270 attributedSize += otherData.gamesSize 271 + otherData.musicAppsSize 272 + otherData.videoAppsSize 273 + otherData.otherAppsSize; 274 attributedSize += otherData.externalStats.totalBytes 275 - otherData.externalStats.appBytes; 276 } 277 278 final long systemSize = Math.max(TrafficStats.GB_IN_BYTES, mUsedBytes - attributedSize); 279 mSystemPreference.setStorageSize(systemSize, mTotalSize); 280 } 281 } 282 283 public void setUsedSize(long usedSizeBytes) { 284 mUsedBytes = usedSizeBytes; 285 } 286 287 public void setTotalSize(long totalSizeBytes) { 288 mTotalSize = totalSizeBytes; 289 } 290 291 /** 292 * Returns a list of keys used by this preference controller. 293 */ 294 public static List<String> getUsedKeys() { 295 List<String> list = new ArrayList<>(); 296 list.add(PHOTO_KEY); 297 list.add(AUDIO_KEY); 298 list.add(GAME_KEY); 299 list.add(MOVIES_KEY); 300 list.add(OTHER_APPS_KEY); 301 list.add(SYSTEM_KEY); 302 list.add(FILES_KEY); 303 return list; 304 } 305 306 private Intent getPhotosIntent() { 307 Intent intent = new Intent(); 308 intent.setAction(android.content.Intent.ACTION_VIEW); 309 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); 310 intent.setType(IMAGE_MIME_TYPE); 311 intent.putExtra(Intent.EXTRA_FROM_STORAGE, true); 312 return intent; 313 } 314 315 private Intent getAudioIntent() { 316 if (mVolume == null) { 317 return null; 318 } 319 320 Bundle args = new Bundle(); 321 args.putString(ManageApplications.EXTRA_CLASSNAME, 322 Settings.StorageUseActivity.class.getName()); 323 args.putString(ManageApplications.EXTRA_VOLUME_UUID, mVolume.getFsUuid()); 324 args.putString(ManageApplications.EXTRA_VOLUME_NAME, mVolume.getDescription()); 325 args.putInt(ManageApplications.EXTRA_STORAGE_TYPE, ManageApplications.STORAGE_TYPE_MUSIC); 326 return Utils.onBuildStartFragmentIntent(mContext, 327 ManageApplications.class.getName(), args, null, R.string.storage_music_audio, null, 328 false, mMetricsFeatureProvider.getMetricsCategory(mFragment)); 329 } 330 331 private Intent getAppsIntent() { 332 if (mVolume == null) { 333 return null; 334 } 335 336 Bundle args = new Bundle(); 337 args.putString(ManageApplications.EXTRA_CLASSNAME, 338 Settings.StorageUseActivity.class.getName()); 339 args.putString(ManageApplications.EXTRA_VOLUME_UUID, mVolume.getFsUuid()); 340 args.putString(ManageApplications.EXTRA_VOLUME_NAME, mVolume.getDescription()); 341 return Utils.onBuildStartFragmentIntent(mContext, 342 ManageApplications.class.getName(), args, null, R.string.apps_storage, null, 343 false, mMetricsFeatureProvider.getMetricsCategory(mFragment)); 344 } 345 346 private Intent getGamesIntent() { 347 Bundle args = new Bundle(1); 348 args.putString(ManageApplications.EXTRA_CLASSNAME, 349 Settings.GamesStorageActivity.class.getName()); 350 return Utils.onBuildStartFragmentIntent(mContext, 351 ManageApplications.class.getName(), args, null, R.string.game_storage_settings, 352 null, false, mMetricsFeatureProvider.getMetricsCategory(mFragment)); 353 } 354 355 private Intent getMoviesIntent() { 356 Bundle args = new Bundle(1); 357 args.putString(ManageApplications.EXTRA_CLASSNAME, 358 Settings.MoviesStorageActivity.class.getName()); 359 return Utils.onBuildStartFragmentIntent(mContext, 360 ManageApplications.class.getName(), args, null, R.string.storage_movies_tv, 361 null, false, mMetricsFeatureProvider.getMetricsCategory(mFragment)); 362 } 363 364 private Intent getFilesIntent() { 365 return mSvp.findEmulatedForPrivate(mVolume).buildBrowseIntent(); 366 } 367 368 private void launchIntent(Intent intent) { 369 try { 370 final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, -1); 371 372 if (userId == -1) { 373 mFragment.startActivity(intent); 374 } else { 375 mFragment.getActivity().startActivityAsUser(intent, new UserHandle(userId)); 376 } 377 } catch (ActivityNotFoundException e) { 378 Log.w(TAG, "No activity found for " + intent); 379 } 380 } 381 382 private static long totalValues(StorageMeasurement.MeasurementDetails details, int userId, 383 String... keys) { 384 long total = 0; 385 Map<String, Long> map = details.mediaSize.get(userId); 386 if (map != null) { 387 for (String key : keys) { 388 if (map.containsKey(key)) { 389 total += map.get(key); 390 } 391 } 392 } else { 393 Log.w(TAG, "MeasurementDetails mediaSize array does not have key for user " + userId); 394 } 395 return total; 396 } 397 } 398