1/*
2 * Copyright (C) 2014 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;
18
19import android.app.ActivityManager;
20import android.content.Intent;
21import android.content.res.Resources;
22import android.graphics.drawable.Drawable;
23import android.os.Bundle;
24import android.os.Environment;
25import android.os.Handler;
26import android.os.Message;
27import android.text.format.Formatter;
28
29import com.android.tv.settings.device.storage.StorageItem;
30import com.android.tv.settings.device.storage.StorageMeasurement;
31import com.android.tv.settings.device.storage.StorageMeasurement.MeasurementDetails;
32import com.android.tv.settings.device.storage.StorageMeasurement.MeasurementReceiver;
33import com.android.tv.settings.device.storage.PercentageBarChart;
34import com.android.tv.settings.device.privacy.PrivacyActivity;
35
36import com.android.tv.settings.dialog.SettingsLayoutActivity;
37import com.android.tv.settings.dialog.Layout;
38import com.android.tv.settings.dialog.Layout.Header;
39import com.android.tv.settings.dialog.Layout.Action;
40import com.android.tv.settings.dialog.Layout.Status;
41import com.android.tv.settings.dialog.Layout.Static;
42import com.android.tv.settings.dialog.Layout.StringGetter;
43import com.android.tv.settings.dialog.Layout.LayoutGetter;
44import com.android.tv.settings.dialog.Layout.DrawableGetter;
45
46import com.android.tv.settings.R;
47
48import java.util.ArrayList;
49import java.util.Collections;
50import java.util.HashMap;
51import java.util.List;
52
53/**
54 * Activity to view storage consumption and factory reset device.
55 */
56public class StorageResetActivity extends SettingsLayoutActivity {
57
58    private static final boolean DEBUG = false;
59    private static final String TAG = "StorageResetActivity";
60    private static final long INVALID_SIZE = -1;
61    private static final int ACTION_RESET_DEVICE = 1;
62    private static final int ACTION_CANCEL = 2;
63
64    /**
65     * Support for shutdown-after-reset. If our launch intent has a true value for
66     * the boolean extra under the following key, then include it in the intent we
67     * use to trigger a factory reset. This will cause us to shut down instead of
68     * restart after the reset.
69     */
70    private static final String SHUTDOWN_INTENT_EXTRA = "shutdown";
71
72    private final MeasurementReceiver mReceiver = new MeasurementReceiver() {
73
74        private MeasurementDetails mLastMeasurementDetails = null;
75
76        @Override
77        public void updateApproximate(StorageMeasurement meas, long totalSize, long availSize) {
78            if (mLastMeasurementDetails == null) {
79                StorageResetActivity.this.updateApproximate(totalSize, availSize);
80            }
81        }
82
83        @Override
84        public void updateDetails(StorageMeasurement meas, MeasurementDetails details) {
85            mLastMeasurementDetails = details;
86            StorageResetActivity.this.updateDetails(mLastMeasurementDetails);
87        }
88    };
89
90    private class SizeStringGetter extends StringGetter {
91        private long mSize = INVALID_SIZE;
92
93        @Override
94        public String get() {
95            return String.format(getString(R.string.storage_size), formatSize(mSize));
96        }
97
98        public void setSize(long size) {
99            mSize = size;
100            refreshView();
101        }
102    };
103
104    private class StorageDrawableGetter extends DrawableGetter {
105        Drawable mDrawable = null;
106
107        @Override
108        public Drawable get() {
109            if (mDrawable == null) {
110                return mRes.getDrawable(R.drawable.ic_settings_storage);
111            } else {
112                return mDrawable;
113            }
114        }
115
116        void setDrawable(ArrayList<PercentageBarChart.Entry> entries) {
117            mDrawable = new PercentageBarChart(entries, mRes.getColor(R.color.storage_avail),
118                getPixelSize(R.dimen.storage_bar_min_tick_width),
119                getPixelSize(R.dimen.content_fragment_icon_width),
120                getPixelSize(R.dimen.content_fragment_icon_width), isLayoutRtl());
121            refreshView();
122        }
123    };
124
125    private Resources mRes;
126    private StorageMeasurement mMeasure;
127    private boolean mResumed = false;
128    private final SizeStringGetter mAppsSize = new SizeStringGetter();
129    private final SizeStringGetter mDcimSize = new SizeStringGetter();
130    private final SizeStringGetter mMusicSize = new SizeStringGetter();
131    private final SizeStringGetter mDownloadsSize = new SizeStringGetter();
132    private final SizeStringGetter mCacheSize = new SizeStringGetter();
133    private final SizeStringGetter mMiscSize = new SizeStringGetter();
134    private final SizeStringGetter mAvailSize = new SizeStringGetter();
135    private final SizeStringGetter mStorageDescription = new SizeStringGetter();
136    private final StorageDrawableGetter mStorageDrawable = new StorageDrawableGetter();
137
138    @Override
139    protected void onCreate(Bundle savedInstanceState) {
140        mRes = getResources();
141        super.onCreate(savedInstanceState);
142        mMeasure = StorageMeasurement.getInstance(this, null);
143    }
144
145    @Override
146    protected void onResume() {
147        super.onResume();
148        mResumed = true;
149        mMeasure.setReceiver(mReceiver);
150        mMeasure.invalidate();
151        mMeasure.measure();
152    }
153
154    @Override
155    protected void onPause() {
156        mMeasure.cleanUp();
157        mResumed = false;
158        super.onPause();
159    }
160
161    @Override
162    public Layout createLayout() {
163        return
164            new Layout().breadcrumb(getString(R.string.header_category_device))
165                .add(new Header.Builder(mRes)
166                        .icon(mStorageDrawable)
167                        .title(R.string.device_storage_reset)
168                        .build()
169                    .add(new Header.Builder(mRes)
170                            .title(R.string.storage_title)
171                            .description(mStorageDescription)
172                            .build()
173                        .add(new Status.Builder(mRes)
174                            .title(R.string.storage_apps_usage)
175                            .icon(R.drawable.storage_indicator_apps)
176                            .description(mAppsSize)
177                            .build())
178                        .add(new Status.Builder(mRes)
179                            .title(R.string.storage_dcim_usage)
180                            .icon(R.drawable.storage_indicator_dcim)
181                            .description(mDcimSize)
182                            .build())
183                        .add(new Status.Builder(mRes)
184                            .title(R.string.storage_music_usage)
185                            .icon(R.drawable.storage_indicator_music)
186                            .description(mMusicSize)
187                            .build())
188                        .add(new Status.Builder(mRes)
189                            .title(R.string.storage_downloads_usage)
190                            .icon(R.drawable.storage_indicator_downloads)
191                            .description(mDownloadsSize)
192                            .build())
193                        .add(new Status.Builder(mRes)
194                            .title(R.string.storage_media_cache_usage)
195                            .icon(R.drawable.storage_indicator_cache)
196                            .description(mCacheSize)
197                            .build())
198                        .add(new Status.Builder(mRes)
199                            .title(R.string.storage_media_misc_usage)
200                            .icon(R.drawable.storage_indicator_misc)
201                            .description(mMiscSize)
202                            .build())
203                        .add(new Status.Builder(mRes)
204                            .title(R.string.storage_available)
205                            .icon(R.drawable.storage_indicator_available)
206                            .description(mAvailSize)
207                            .build())
208                    )
209                    .add(new Header.Builder(mRes)
210                            .title(R.string.device_reset)
211                            .build()
212                        .add(new Header.Builder(mRes)
213                                .title(R.string.device_reset)
214                                .build()
215                            .add(new Action.Builder(mRes, ACTION_RESET_DEVICE)
216                                    .title(R.string.confirm_factory_reset_device)
217                                    .build()
218                            )
219                            .add(new Action.Builder(mRes, Action.ACTION_BACK)
220                                .title(R.string.title_cancel)
221                                .defaultSelection()
222                                .build())
223                        )
224                        .add(new Action.Builder(mRes, Action.ACTION_BACK)
225                            .title(R.string.title_cancel)
226                            .defaultSelection()
227                            .build())
228                    )
229                );
230    }
231
232    @Override
233    public void onActionClicked(Action action) {
234        switch (action.getId()) {
235            case ACTION_RESET_DEVICE:
236                if (!ActivityManager.isUserAMonkey()) {
237                    Intent resetIntent = new Intent("android.intent.action.MASTER_CLEAR");
238                    if (getIntent().getBooleanExtra(SHUTDOWN_INTENT_EXTRA, false)) {
239                        resetIntent.putExtra(SHUTDOWN_INTENT_EXTRA, true);
240                    }
241                    sendBroadcast(resetIntent);
242                }
243                break;
244            case ACTION_CANCEL:
245                goBackToTitle(getString(R.string.device_storage_reset));
246                break;
247        }
248    }
249
250    private void updateStorageSize(long availSize, long appsSize, long dcimSize, long musicSize,
251            long downloadsSize, long cacheSize, long miscSize) {
252        mAvailSize.setSize(availSize);
253        mAppsSize.setSize(appsSize);
254        mDcimSize.setSize(dcimSize);
255        mMusicSize.setSize(musicSize);
256        mDownloadsSize.setSize(downloadsSize);
257        mCacheSize.setSize(cacheSize);
258        mMiscSize.setSize(miscSize);
259    }
260
261    private int getPixelSize(int resource) {
262        return mRes.getDimensionPixelSize(resource);
263    }
264
265    void updateStorageDescription(long totalSize, ArrayList<PercentageBarChart.Entry> entries) {
266        mStorageDescription.setSize(totalSize);
267        mStorageDrawable.setDrawable(entries);
268    }
269
270    private void updateApproximate(long totalSize, long availSize) {
271
272        final long usedSize = totalSize - availSize;
273        ArrayList<PercentageBarChart.Entry> entries = new ArrayList<PercentageBarChart.Entry>();
274        entries.add(new PercentageBarChart.Entry(
275                0, usedSize / (float) totalSize, android.graphics.Color.GRAY));
276
277        updateStorageSize(availSize, INVALID_SIZE, INVALID_SIZE, INVALID_SIZE, INVALID_SIZE,
278                INVALID_SIZE, INVALID_SIZE);
279        updateStorageDescription(totalSize, entries);
280    }
281
282    private void addEntry(List<PercentageBarChart.Entry> entries, StorageItem storageItem,
283            long size, long totalSize) {
284        if (size > 0) {
285            entries.add(new PercentageBarChart.Entry(
286                    storageItem.ordinal(), size / (float) totalSize,
287                    storageItem.getColor(getResources())));
288        }
289    }
290
291    private void updateDetails(MeasurementDetails details) {
292
293        final long dcimSize = totalValues(details.mediaSize, Environment.DIRECTORY_DCIM,
294                Environment.DIRECTORY_MOVIES, Environment.DIRECTORY_PICTURES);
295
296        final long musicSize = totalValues(details.mediaSize, Environment.DIRECTORY_MUSIC,
297                Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS,
298                Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_PODCASTS);
299
300        final long downloadsSize = totalValues(details.mediaSize, Environment.DIRECTORY_DOWNLOADS);
301
302        ArrayList<PercentageBarChart.Entry> entries = new ArrayList<PercentageBarChart.Entry>();
303
304        addEntry(entries, StorageItem.APPS, details.appsSize, details.totalSize);
305        addEntry(entries, StorageItem.PICTURES_VIDEO, dcimSize, details.totalSize);
306        addEntry(entries, StorageItem.AUDIO, musicSize, details.totalSize);
307        addEntry(entries, StorageItem.DOWNLOADS, downloadsSize, details.totalSize);
308        addEntry(entries, StorageItem.CACHED_DATA, details.cacheSize, details.totalSize);
309        addEntry(entries, StorageItem.MISC, details.miscSize, details.totalSize);
310
311        Collections.sort(entries);
312
313        updateStorageSize(details.availSize, details.appsSize, dcimSize, musicSize, downloadsSize,
314                details.cacheSize, details.miscSize);
315        updateStorageDescription(details.totalSize, entries);
316    }
317
318    private static long totalValues(HashMap<String, Long> map, String... keys) {
319        long total = 0;
320        for (String key : keys) {
321            total += map.get(key);
322        }
323        return total;
324    }
325
326    private String formatSize(long size) {
327        return (size == INVALID_SIZE) ? getString(R.string.storage_calculating_size)
328                : Formatter.formatShortFileSize(this, size);
329    }
330}
331