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
17package com.android.tv.settings.device.storage;
18
19import android.app.ActivityManager;
20import android.content.pm.PackageManager;
21import android.os.Bundle;
22import android.os.Environment;
23import android.os.storage.StorageManager;
24import android.os.storage.VolumeInfo;
25import android.support.v17.preference.LeanbackPreferenceFragment;
26import android.support.v7.preference.Preference;
27import android.support.v7.preference.PreferenceScreen;
28import android.text.TextUtils;
29import android.util.Log;
30
31import com.android.settingslib.deviceinfo.StorageMeasurement;
32import com.android.tv.settings.R;
33import com.android.tv.settings.device.apps.AppsFragment;
34
35import java.util.HashMap;
36import java.util.List;
37
38public class StorageFragment extends LeanbackPreferenceFragment {
39    private static final String TAG = "StorageFragment";
40
41    private static final String KEY_MIGRATE = "migrate";
42    private static final String KEY_EJECT = "eject";
43    private static final String KEY_ERASE = "erase";
44    private static final String KEY_APPS_USAGE = "apps_usage";
45    private static final String KEY_DCIM_USAGE = "dcim_usage";
46    private static final String KEY_MUSIC_USAGE = "music_usage";
47    private static final String KEY_DOWNLOADS_USAGE = "downloads_usage";
48    private static final String KEY_CACHE_USAGE = "cache_usage";
49    private static final String KEY_MISC_USAGE = "misc_usage";
50    private static final String KEY_AVAILABLE = "available";
51
52    private StorageManager mStorageManager;
53    private PackageManager mPackageManager;
54
55    private VolumeInfo mVolumeInfo;
56
57    private StorageMeasurement mMeasure;
58    private final StorageMeasurement.MeasurementReceiver mMeasurementReceiver =
59            new MeasurementReceiver();
60    private final StorageEventListener mStorageEventListener = new StorageEventListener();
61
62    private Preference mMigratePref;
63    private Preference mEjectPref;
64    private Preference mErasePref;
65    private StoragePreference mAppsUsagePref;
66    private StoragePreference mDcimUsagePref;
67    private StoragePreference mMusicUsagePref;
68    private StoragePreference mDownloadsUsagePref;
69    private StoragePreference mCacheUsagePref;
70    private StoragePreference mMiscUsagePref;
71    private StoragePreference mAvailablePref;
72
73    public static void prepareArgs(Bundle bundle, VolumeInfo volumeInfo) {
74        bundle.putString(VolumeInfo.EXTRA_VOLUME_ID, volumeInfo.getId());
75    }
76
77    @Override
78    public void onCreate(Bundle savedInstanceState) {
79        mStorageManager = getContext().getSystemService(StorageManager.class);
80        mPackageManager = getContext().getPackageManager();
81
82        mVolumeInfo = mStorageManager.findVolumeById(
83                getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID));
84
85        super.onCreate(savedInstanceState);
86    }
87
88    @Override
89    public void onStart() {
90        super.onStart();
91        mStorageManager.registerListener(mStorageEventListener);
92        startMeasurement();
93    }
94
95    @Override
96    public void onResume() {
97        super.onResume();
98        mVolumeInfo = mStorageManager.findVolumeById(
99                getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID));
100        if (mVolumeInfo == null || !mVolumeInfo.isMountedReadable()) {
101            getFragmentManager().popBackStack();
102        } else {
103            refresh();
104        }
105    }
106
107    @Override
108    public void onStop() {
109        super.onStop();
110        mStorageManager.unregisterListener(mStorageEventListener);
111        stopMeasurement();
112    }
113
114    @Override
115    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
116        setPreferencesFromResource(R.xml.storage, null);
117
118        final PreferenceScreen screen = getPreferenceScreen();
119        screen.setTitle(mStorageManager.getBestVolumeDescription(mVolumeInfo));
120
121        mMigratePref = findPreference(KEY_MIGRATE);
122        mEjectPref = findPreference(KEY_EJECT);
123        mErasePref = findPreference(KEY_ERASE);
124
125        mAppsUsagePref = (StoragePreference) findPreference(KEY_APPS_USAGE);
126        mDcimUsagePref = (StoragePreference) findPreference(KEY_DCIM_USAGE);
127        mMusicUsagePref = (StoragePreference) findPreference(KEY_MUSIC_USAGE);
128        mDownloadsUsagePref = (StoragePreference) findPreference(KEY_DOWNLOADS_USAGE);
129        mCacheUsagePref = (StoragePreference) findPreference(KEY_CACHE_USAGE);
130        mMiscUsagePref = (StoragePreference) findPreference(KEY_MISC_USAGE);
131        mAvailablePref = (StoragePreference) findPreference(KEY_AVAILABLE);
132    }
133
134    private void refresh() {
135        boolean showMigrate = false;
136        final VolumeInfo currentExternal = mPackageManager.getPrimaryStorageCurrentVolume();
137        // currentExternal will be null if the drive is not mounted. Don't offer the option to
138        // migrate if so.
139        if (currentExternal != null
140                && !TextUtils.equals(currentExternal.getId(), mVolumeInfo.getId())) {
141            final List<VolumeInfo> candidates =
142                    mPackageManager.getPrimaryStorageCandidateVolumes();
143            for (final VolumeInfo candidate : candidates) {
144                if (TextUtils.equals(candidate.getId(), mVolumeInfo.getId())) {
145                    showMigrate = true;
146                    break;
147                }
148            }
149        }
150
151        mMigratePref.setVisible(showMigrate);
152        mMigratePref.setIntent(
153                MigrateStorageActivity.getLaunchIntent(getContext(), mVolumeInfo.getId(), true));
154
155        final String description = mStorageManager.getBestVolumeDescription(mVolumeInfo);
156
157        final boolean privateInternal = VolumeInfo.ID_PRIVATE_INTERNAL.equals(mVolumeInfo.getId());
158        final boolean isPrivate = mVolumeInfo.getType() == VolumeInfo.TYPE_PRIVATE;
159
160        mEjectPref.setVisible(!privateInternal);
161        mEjectPref.setIntent(UnmountActivity.getIntent(getContext(), mVolumeInfo.getId(),
162                description));
163        mErasePref.setVisible(!privateInternal);
164        if (isPrivate) {
165            mErasePref.setIntent(
166                    FormatActivity.getFormatAsPublicIntent(getContext(), mVolumeInfo.getDiskId()));
167            mErasePref.setTitle(R.string.storage_format_as_public);
168        } else {
169            mErasePref.setIntent(
170                    FormatActivity.getFormatAsPrivateIntent(getContext(), mVolumeInfo.getDiskId()));
171            mErasePref.setTitle(R.string.storage_format_as_private);
172        }
173
174        mAppsUsagePref.setVisible(isPrivate);
175        mAppsUsagePref.setFragment(AppsFragment.class.getName());
176        AppsFragment.prepareArgs(mAppsUsagePref.getExtras(), mVolumeInfo.fsUuid, description);
177        mDcimUsagePref.setVisible(isPrivate);
178        mMusicUsagePref.setVisible(isPrivate);
179        mDownloadsUsagePref.setVisible(isPrivate);
180        mCacheUsagePref.setVisible(isPrivate);
181        mCacheUsagePref.setFragment(ConfirmClearCacheFragment.class.getName());
182    }
183
184    private void startMeasurement() {
185        if (mVolumeInfo != null && mVolumeInfo.isMountedReadable()) {
186            final VolumeInfo sharedVolume = mStorageManager.findEmulatedForPrivate(mVolumeInfo);
187            mMeasure = new StorageMeasurement(getContext(), mVolumeInfo, sharedVolume);
188            mMeasure.setReceiver(mMeasurementReceiver);
189            mMeasure.forceMeasure();
190        }
191    }
192
193    private void stopMeasurement() {
194        if (mMeasure != null) {
195            mMeasure.onDestroy();
196        }
197    }
198
199    private void updateDetails(StorageMeasurement.MeasurementDetails details) {
200        final int currentUser = ActivityManager.getCurrentUser();
201        final long dcimSize = totalValues(details.mediaSize.get(currentUser),
202                Environment.DIRECTORY_DCIM,
203                Environment.DIRECTORY_MOVIES, Environment.DIRECTORY_PICTURES);
204
205        final long musicSize = totalValues(details.mediaSize.get(currentUser),
206                Environment.DIRECTORY_MUSIC,
207                Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS,
208                Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_PODCASTS);
209
210        final long downloadsSize = totalValues(details.mediaSize.get(currentUser),
211                Environment.DIRECTORY_DOWNLOADS);
212
213        mAvailablePref.setSize(details.availSize);
214        mAppsUsagePref.setSize(details.appsSize.get(currentUser));
215        mDcimUsagePref.setSize(dcimSize);
216        mMusicUsagePref.setSize(musicSize);
217        mDownloadsUsagePref.setSize(downloadsSize);
218        mCacheUsagePref.setSize(details.cacheSize);
219        mMiscUsagePref.setSize(details.miscSize.get(currentUser));
220    }
221
222    private static long totalValues(HashMap<String, Long> map, String... keys) {
223        long total = 0;
224        if (map != null) {
225            for (String key : keys) {
226                if (map.containsKey(key)) {
227                    total += map.get(key);
228                }
229            }
230        } else {
231            Log.w(TAG,
232                    "MeasurementDetails mediaSize array does not have key for current user " +
233                            ActivityManager.getCurrentUser());
234        }
235        return total;
236    }
237
238    private class MeasurementReceiver implements StorageMeasurement.MeasurementReceiver {
239
240        @Override
241        public void onDetailsChanged(StorageMeasurement.MeasurementDetails details) {
242            updateDetails(details);
243        }
244    }
245
246    private class StorageEventListener extends android.os.storage.StorageEventListener {
247        @Override
248        public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
249            mVolumeInfo = vol;
250            if (isResumed()) {
251                if (mVolumeInfo.isMountedReadable()) {
252                    refresh();
253                } else {
254                    getFragmentManager().popBackStack();
255                }
256            }
257        }
258    }
259}
260