blob: 6ae4a6f0b202757c09a7a40069c7fee101a4d66b [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.deviceinfo.storage;
import android.app.Fragment;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.net.TrafficStats;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.storage.VolumeInfo;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.Settings;
import com.android.settings.applications.manageapplications.ManageApplications;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.deviceinfo.PrivateVolumeSettings.SystemInfoFragment;
import com.android.settings.deviceinfo.StorageItemPreference;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.deviceinfo.StorageMeasurement;
import com.android.settingslib.deviceinfo.StorageVolumeProvider;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* StorageItemPreferenceController handles the storage line items which summarize the storage
* categorization breakdown.
*/
public class StorageItemPreferenceController extends AbstractPreferenceController implements
PreferenceControllerMixin {
private static final String TAG = "StorageItemPreference";
private static final String SYSTEM_FRAGMENT_TAG = "SystemInfo";
@VisibleForTesting
static final String PHOTO_KEY = "pref_photos_videos";
@VisibleForTesting
static final String AUDIO_KEY = "pref_music_audio";
@VisibleForTesting
static final String GAME_KEY = "pref_games";
@VisibleForTesting
static final String MOVIES_KEY = "pref_movies";
@VisibleForTesting
static final String OTHER_APPS_KEY = "pref_other_apps";
@VisibleForTesting
static final String SYSTEM_KEY = "pref_system";
@VisibleForTesting
static final String FILES_KEY = "pref_files";
private final Fragment mFragment;
private final MetricsFeatureProvider mMetricsFeatureProvider;
private final StorageVolumeProvider mSvp;
private VolumeInfo mVolume;
private int mUserId;
private long mUsedBytes;
private long mTotalSize;
private PreferenceScreen mScreen;
private StorageItemPreference mPhotoPreference;
private StorageItemPreference mAudioPreference;
private StorageItemPreference mGamePreference;
private StorageItemPreference mMoviesPreference;
private StorageItemPreference mAppPreference;
private StorageItemPreference mFilePreference;
private StorageItemPreference mSystemPreference;
private boolean mIsWorkProfile;
private static final String AUTHORITY_MEDIA = "com.android.providers.media.documents";
public StorageItemPreferenceController(
Context context, Fragment hostFragment, VolumeInfo volume, StorageVolumeProvider svp) {
super(context);
mFragment = hostFragment;
mVolume = volume;
mSvp = svp;
mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
mUserId = UserHandle.myUserId();
}
public StorageItemPreferenceController(
Context context,
Fragment hostFragment,
VolumeInfo volume,
StorageVolumeProvider svp,
boolean isWorkProfile) {
this(context, hostFragment, volume, svp);
mIsWorkProfile = isWorkProfile;
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (preference == null) {
return false;
}
Intent intent = null;
if (preference.getKey() == null) {
return false;
}
switch (preference.getKey()) {
case PHOTO_KEY:
intent = getPhotosIntent();
break;
case AUDIO_KEY:
intent = getAudioIntent();
break;
case GAME_KEY:
intent = getGamesIntent();
break;
case MOVIES_KEY:
intent = getMoviesIntent();
break;
case OTHER_APPS_KEY:
// Because we are likely constructed with a null volume, this is theoretically
// possible.
if (mVolume == null) {
break;
}
intent = getAppsIntent();
break;
case FILES_KEY:
intent = getFilesIntent();
FeatureFactory.getFactory(mContext).getMetricsFeatureProvider().action(
mContext, MetricsEvent.STORAGE_FILES);
break;
case SYSTEM_KEY:
final SystemInfoFragment dialog = new SystemInfoFragment();
dialog.setTargetFragment(mFragment, 0);
dialog.show(mFragment.getFragmentManager(), SYSTEM_FRAGMENT_TAG);
return true;
}
if (intent != null) {
intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
launchIntent(intent);
return true;
}
return super.handlePreferenceTreeClick(preference);
}
@Override
public String getPreferenceKey() {
return null;
}
/**
* Sets the storage volume to use for when handling taps.
*/
public void setVolume(VolumeInfo volume) {
mVolume = volume;
setFilesPreferenceVisibility();
}
private void setFilesPreferenceVisibility() {
if (mScreen != null) {
final VolumeInfo sharedVolume = mSvp.findEmulatedForPrivate(mVolume);
// If we don't have a shared volume for our internal storage (or the shared volume isn't
// mounted as readable for whatever reason), we should hide the File preference.
final boolean hideFilePreference =
(sharedVolume == null) || !sharedVolume.isMountedReadable();
if (hideFilePreference) {
mScreen.removePreference(mFilePreference);
} else {
mScreen.addPreference(mFilePreference);
}
}
}
/**
* Sets the user id for which this preference controller is handling.
*/
public void setUserId(UserHandle userHandle) {
mUserId = userHandle.getIdentifier();
tintPreference(mPhotoPreference);
tintPreference(mMoviesPreference);
tintPreference(mAudioPreference);
tintPreference(mGamePreference);
tintPreference(mAppPreference);
tintPreference(mSystemPreference);
tintPreference(mFilePreference);
}
private void tintPreference(Preference preference) {
if (preference != null) {
preference.setIcon(applyTint(mContext, preference.getIcon()));
}
}
private static Drawable applyTint(Context context, Drawable icon) {
TypedArray array =
context.obtainStyledAttributes(new int[] {android.R.attr.colorControlNormal});
icon = icon.mutate();
icon.setTint(array.getColor(0, 0));
array.recycle();
return icon;
}
@Override
public void displayPreference(PreferenceScreen screen) {
mScreen = screen;
mPhotoPreference = (StorageItemPreference) screen.findPreference(PHOTO_KEY);
mAudioPreference = (StorageItemPreference) screen.findPreference(AUDIO_KEY);
mGamePreference = (StorageItemPreference) screen.findPreference(GAME_KEY);
mMoviesPreference = (StorageItemPreference) screen.findPreference(MOVIES_KEY);
mAppPreference = (StorageItemPreference) screen.findPreference(OTHER_APPS_KEY);
mSystemPreference = (StorageItemPreference) screen.findPreference(SYSTEM_KEY);
mFilePreference = (StorageItemPreference) screen.findPreference(FILES_KEY);
setFilesPreferenceVisibility();
}
public void onLoadFinished(SparseArray<StorageAsyncLoader.AppsStorageResult> result,
int userId) {
final StorageAsyncLoader.AppsStorageResult data = result.get(userId);
// TODO(b/35927909): Figure out how to split out apps which are only installed for work
// profiles in order to attribute those app's code bytes only to that profile.
mPhotoPreference.setStorageSize(
data.photosAppsSize + data.externalStats.imageBytes + data.externalStats.videoBytes,
mTotalSize);
mAudioPreference.setStorageSize(
data.musicAppsSize + data.externalStats.audioBytes, mTotalSize);
mGamePreference.setStorageSize(data.gamesSize, mTotalSize);
mMoviesPreference.setStorageSize(data.videoAppsSize, mTotalSize);
mAppPreference.setStorageSize(data.otherAppsSize, mTotalSize);
long otherExternalBytes =
data.externalStats.totalBytes
- data.externalStats.audioBytes
- data.externalStats.videoBytes
- data.externalStats.imageBytes
- data.externalStats.appBytes;
mFilePreference.setStorageSize(otherExternalBytes, mTotalSize);
if (mSystemPreference != null) {
// Everything else that hasn't already been attributed is tracked as
// belonging to system.
long attributedSize = 0;
for (int i = 0; i < result.size(); i++) {
final StorageAsyncLoader.AppsStorageResult otherData = result.valueAt(i);
attributedSize +=
otherData.gamesSize
+ otherData.musicAppsSize
+ otherData.videoAppsSize
+ otherData.photosAppsSize
+ otherData.otherAppsSize;
attributedSize += otherData.externalStats.totalBytes
- otherData.externalStats.appBytes;
}
final long systemSize = Math.max(TrafficStats.GB_IN_BYTES, mUsedBytes - attributedSize);
mSystemPreference.setStorageSize(systemSize, mTotalSize);
}
}
public void setUsedSize(long usedSizeBytes) {
mUsedBytes = usedSizeBytes;
}
public void setTotalSize(long totalSizeBytes) {
mTotalSize = totalSizeBytes;
}
/**
* Returns a list of keys used by this preference controller.
*/
public static List<String> getUsedKeys() {
List<String> list = new ArrayList<>();
list.add(PHOTO_KEY);
list.add(AUDIO_KEY);
list.add(GAME_KEY);
list.add(MOVIES_KEY);
list.add(OTHER_APPS_KEY);
list.add(SYSTEM_KEY);
list.add(FILES_KEY);
return list;
}
private Intent getPhotosIntent() {
Bundle args = getWorkAnnotatedBundle(2);
args.putString(
ManageApplications.EXTRA_CLASSNAME, Settings.PhotosStorageActivity.class.getName());
args.putInt(
ManageApplications.EXTRA_STORAGE_TYPE,
ManageApplications.STORAGE_TYPE_PHOTOS_VIDEOS);
return new SubSettingLauncher(mContext)
.setDestination(ManageApplications.class.getName())
.setTitle(R.string.storage_photos_videos)
.setArguments(args)
.setSourceMetricsCategory(mMetricsFeatureProvider.getMetricsCategory(mFragment))
.toIntent();
}
private Intent getAudioIntent() {
if (mVolume == null) {
return null;
}
Bundle args = getWorkAnnotatedBundle(4);
args.putString(ManageApplications.EXTRA_CLASSNAME,
Settings.StorageUseActivity.class.getName());
args.putString(ManageApplications.EXTRA_VOLUME_UUID, mVolume.getFsUuid());
args.putString(ManageApplications.EXTRA_VOLUME_NAME, mVolume.getDescription());
args.putInt(ManageApplications.EXTRA_STORAGE_TYPE, ManageApplications.STORAGE_TYPE_MUSIC);
return new SubSettingLauncher(mContext)
.setDestination(ManageApplications.class.getName())
.setTitle(R.string.storage_music_audio)
.setArguments(args)
.setSourceMetricsCategory(mMetricsFeatureProvider.getMetricsCategory(mFragment))
.toIntent();
}
private Intent getAppsIntent() {
if (mVolume == null) {
return null;
}
final Bundle args = getWorkAnnotatedBundle(3);
args.putString(ManageApplications.EXTRA_CLASSNAME,
Settings.StorageUseActivity.class.getName());
args.putString(ManageApplications.EXTRA_VOLUME_UUID, mVolume.getFsUuid());
args.putString(ManageApplications.EXTRA_VOLUME_NAME, mVolume.getDescription());
return new SubSettingLauncher(mContext)
.setDestination(ManageApplications.class.getName())
.setTitle(R.string.apps_storage)
.setArguments(args)
.setSourceMetricsCategory(mMetricsFeatureProvider.getMetricsCategory(mFragment))
.toIntent();
}
private Intent getGamesIntent() {
final Bundle args = getWorkAnnotatedBundle(1);
args.putString(ManageApplications.EXTRA_CLASSNAME,
Settings.GamesStorageActivity.class.getName());
return new SubSettingLauncher(mContext)
.setDestination(ManageApplications.class.getName())
.setTitle(R.string.game_storage_settings)
.setArguments(args)
.setSourceMetricsCategory(mMetricsFeatureProvider.getMetricsCategory(mFragment))
.toIntent();
}
private Intent getMoviesIntent() {
final Bundle args = getWorkAnnotatedBundle(1);
args.putString(ManageApplications.EXTRA_CLASSNAME,
Settings.MoviesStorageActivity.class.getName());
return new SubSettingLauncher(mContext)
.setDestination(ManageApplications.class.getName())
.setTitle(R.string.storage_movies_tv)
.setArguments(args)
.setSourceMetricsCategory(mMetricsFeatureProvider.getMetricsCategory(mFragment))
.toIntent();
}
private Bundle getWorkAnnotatedBundle(int additionalCapacity) {
final Bundle args = new Bundle(2 + additionalCapacity);
args.putBoolean(ManageApplications.EXTRA_WORK_ONLY, mIsWorkProfile);
args.putInt(ManageApplications.EXTRA_WORK_ID, mUserId);
return args;
}
private Intent getFilesIntent() {
return mSvp.findEmulatedForPrivate(mVolume).buildBrowseIntent();
}
private void launchIntent(Intent intent) {
try {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, -1);
if (userId == -1) {
mFragment.startActivity(intent);
} else {
mFragment.getActivity().startActivityAsUser(intent, new UserHandle(userId));
}
} catch (ActivityNotFoundException e) {
Log.w(TAG, "No activity found for " + intent);
}
}
private static long totalValues(StorageMeasurement.MeasurementDetails details, int userId,
String... keys) {
long total = 0;
Map<String, Long> map = details.mediaSize.get(userId);
if (map != null) {
for (String key : keys) {
if (map.containsKey(key)) {
total += map.get(key);
}
}
} else {
Log.w(TAG, "MeasurementDetails mediaSize array does not have key for user " + userId);
}
return total;
}
}