OSDN Git Service

CMFileManager: add usage stats by mime type
authorMartin Brabham <optedoblivion@cyngn.com>
Mon, 15 Dec 2014 18:28:45 +0000 (10:28 -0800)
committerMartin Brabham <optedoblivion@cyngn.com>
Tue, 16 Dec 2014 19:51:44 +0000 (11:51 -0800)
Change-Id: I92ffb45ec3ef0dc6feb8b8129bcb0fedf442ba54

13 files changed:
AndroidManifest.xml
res/layout/disk_usage_category_view.xml [new file with mode: 0644]
res/layout/filesystem_info_dialog.xml
res/values/colors.xml
res/values/dimen.xml
src/com/cyanogenmod/filemanager/FileManagerApplication.java
src/com/cyanogenmod/filemanager/model/DiskUsage.java
src/com/cyanogenmod/filemanager/model/DiskUsageCategory.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/providers/MimeTypeIndexProvider.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/service/MimeTypeIndexService.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/tasks/FetchStatsByTypeTask.java [new file with mode: 0644]
src/com/cyanogenmod/filemanager/ui/dialogs/FilesystemInfoDialog.java
src/com/cyanogenmod/filemanager/ui/widgets/DiskUsageGraph.java

index d442b2a..88df3cd 100644 (file)
       android:grantUriPermissions="true"
       android:exported="true" />
 
+    <provider
+      android:authorities="com.cyanogenmod.filemanager.providers.index"
+      android:name=".providers.MimeTypeIndexProvider"/>
+
+    <service
+      android:name=".service.MimeTypeIndexService"
+      android:label="@string/app_name">
+      <intent-filter>
+        <action android:name="com.cyanogenmod.filemanager.ACTION_START_INDEX"/>
+      </intent-filter>
+    </service>
+
     <activity
       android:name=".activities.NavigationActivity"
       android:label="@string/app_name"
diff --git a/res/layout/disk_usage_category_view.xml b/res/layout/disk_usage_category_view.xml
new file mode 100644 (file)
index 0000000..5b4c5c4
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 The CyanogenMod 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:weightSum="1.0"
+    android:gravity="center_vertical">
+
+    <View
+        android:id="@+id/v_legend_swatch"
+        android:layout_width="10dp"
+        android:layout_height="10dp"
+        android:layout_weight=".2"/>
+
+    <TextView
+        android:id="@+id/tv_legend_title"
+        android:textSize="@dimen/legend_title_textSize"
+        android:paddingLeft="@dimen/legend_title_paddingLeft"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight=".8"/>
+
+</LinearLayout>
index 4b51232..0730339 100644 (file)
     </TableLayout>
   </LinearLayout>
 
+  <GridLayout
+    android:id="@+id/ll_legend"
+    android:gravity="center"
+    android:layout_centerHorizontal="true"
+    android:visibility="invisible"
+    android:layout_below="@id/filesystem_tab_diskusage"
+    android:orientation="horizontal"
+    android:columnCount="4"
+    android:columnOrderPreserved="true"
+    android:rowOrderPreserved="true"
+    android:useDefaultMargins="true"
+    android:padding="@dimen/legend_padding"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"/>
+
 </RelativeLayout>
index 09d84d5..123860c 100644 (file)
     <color name="material_palette_blue_primary">#1E88E5</color>
     <color name="material_palette_blue_primary_dark">#1976D2</color>
     <color name="actionbar_palette_blue_primary">#1E88E5</color>
+
+
+    <!-- Disk usage blue -->
+    <color name="material_palette_blue_1">#64B5F6</color>
+    <color name="material_palette_blue_2">#2196F3</color>
+    <color name="material_palette_blue_3">#1976D2</color>
+    <color name="material_palette_blue_4">#0D47A1</color>
+
+    <!-- Disk usage green -->
+    <color name="material_palette_green_1">#DCE775</color>
+    <color name="material_palette_green_2">#CDDC39</color>
+    <color name="material_palette_green_3">#AFB42B</color>
+    <color name="material_palette_green_4">#827717</color>
+
+    <!-- Disk usage orange -->
+    <color name="material_palette_orange_1">#FFB74D</color>
+    <color name="material_palette_orange_2">#FF9800</color>
+    <color name="material_palette_orange_3">#F57C00</color>
+    <color name="material_palette_orange_4">#E65100</color>
+
+    <!-- Disk usage pink -->
+    <color name="material_palette_pink_1">#F06292</color>
+    <color name="material_palette_pink_2">#E91E63</color>
+    <color name="material_palette_pink_3">#C2185B</color>
+    <color name="material_palette_pink_4">#880E4F</color>
+
 </resources>
index 5bd2fc1..58307cb 100644 (file)
     <dimen name="scrim_layout_elevation">10dp</dimen>
     <!-- Drawer header height -->
     <dimen name="drawer_header_height">150dp</dimen>
+
+    <!-- usage graph stuff -->
+    <dimen name="legend_title_textSize">9sp</dimen>
+    <dimen name="legend_title_paddingLeft">3dp</dimen>
+    <dimen name="legend_padding">10dp</dimen>
+
 </resources>
index ebf369a..0529b97 100644 (file)
@@ -22,6 +22,7 @@ import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
+import android.os.Environment;
 import android.util.Log;
 
 import com.cyanogenmod.filemanager.console.Console;
@@ -34,6 +35,7 @@ import com.cyanogenmod.filemanager.preferences.AccessMode;
 import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
 import com.cyanogenmod.filemanager.preferences.ObjectStringIdentifier;
 import com.cyanogenmod.filemanager.preferences.Preferences;
+import com.cyanogenmod.filemanager.service.MimeTypeIndexService;
 import com.cyanogenmod.filemanager.ui.ThemeManager;
 import com.cyanogenmod.filemanager.ui.ThemeManager.Theme;
 import com.cyanogenmod.filemanager.util.AIDHelper;
@@ -174,6 +176,13 @@ public final class FileManagerApplication extends Application {
         }
         init();
         register();
+
+        // Kick off usage by mime type indexing for external storage; most likely use case for
+        // file manager
+        File externalStorage = Environment.getExternalStorageDirectory();
+        MimeTypeIndexService.indexFileRoot(this, externalStorage.getAbsolutePath());
+        MimeTypeIndexService.indexFileRoot(this, "/system");
+
     }
 
     /**
index 693e39b..5ce49bf 100644 (file)
@@ -17,6 +17,8 @@
 package com.cyanogenmod.filemanager.model;
 
 import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * A class that holds information about the usage of a disk (total, used and free space).
@@ -25,6 +27,8 @@ public class DiskUsage implements Serializable {
 
     private static final long serialVersionUID = -4540446701543226294L;
 
+    private final List<DiskUsageCategory> mDiskUsageCategoryList =
+            new ArrayList<DiskUsageCategory>();
     private final String mMountPoint;
     private final long mTotal;
     private final long mUsed;
@@ -83,6 +87,44 @@ public class DiskUsage implements Serializable {
     }
 
     /**
+     * Method that returns the total sum of all categories
+     *
+     * @return {@link java.lang.Long}
+     */
+    public long getCategorySum() {
+        long bytes = 0;
+        for (DiskUsageCategory category : getUsageCategoryList()) {
+            bytes += category.getSizeBytes();
+        }
+        return bytes;
+    }
+
+    /**
+     * Add a usage category
+     *
+     * @param category {@link com.cyanogenmod.filemanager.model.DiskUsageCategory} not null
+     *
+     * @throws IllegalArgumentException {@link java.lang.IllegalArgumentException}
+     */
+    public void addUsageCategory(DiskUsageCategory category) throws IllegalArgumentException {
+        if (category == null) {
+            throw new IllegalArgumentException("'category' cannot be null!");
+        }
+        mDiskUsageCategoryList.add(category);
+    }
+
+    public List<DiskUsageCategory> getUsageCategoryList() {
+        return mDiskUsageCategoryList;
+    }
+
+    /**
+     * Clears the list of usage categories
+     */
+    public void clearUsageCategories() {
+        mDiskUsageCategoryList.clear();
+    }
+
+    /**
      * {@inheritDoc}
      */
     @Override
@@ -141,6 +183,4 @@ public class DiskUsage implements Serializable {
                 + this.mFree + "]";  //$NON-NLS-1$
     }
 
-
-
 }
diff --git a/src/com/cyanogenmod/filemanager/model/DiskUsageCategory.java b/src/com/cyanogenmod/filemanager/model/DiskUsageCategory.java
new file mode 100644 (file)
index 0000000..e1be6a0
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+* Copyright (C) 2014 The CyanogenMod 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.cyanogenmod.filemanager.model;
+
+import com.cyanogenmod.filemanager.util.MimeTypeHelper.MimeTypeCategory;
+
+/**
+ * DiskUsageCategory
+ * <pre>
+ *     Category by mime type and the amount of bytes it is using
+ * </pre>
+ */
+public class DiskUsageCategory {
+
+    // Members
+    private MimeTypeCategory mCategory;
+    private long mSizeBytes = 0l;
+
+    /**
+     * Simple constructor
+     */
+    public DiskUsageCategory() {
+    }
+
+    /**
+     * Constructor
+     *
+     * @param category  {@link com.cyanogenmod.filemanager.util.MimeTypeHelper.MimeTypeCategory}
+     * @param sizeBytes {@link java.lang.Long}
+     *
+     * @throws IllegalArgumentException {@link java.lang.IllegalArgumentException}
+     */
+    public DiskUsageCategory(MimeTypeCategory category, long sizeBytes)
+            throws IllegalArgumentException {
+        if (category == null) {
+            throw new IllegalArgumentException("'category' may not be null!");
+        }
+        mCategory = category;
+        mSizeBytes = sizeBytes;
+    }
+
+    public MimeTypeCategory getCategory() {
+        return mCategory;
+    }
+
+    public void setCategory(MimeTypeCategory category) {
+        mCategory = category;
+    }
+
+    public long getSizeBytes() {
+        return mSizeBytes;
+    }
+
+    public void setSizeBytes(long sizeBytes) {
+        mSizeBytes = sizeBytes;
+    }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/providers/MimeTypeIndexProvider.java b/src/com/cyanogenmod/filemanager/providers/MimeTypeIndexProvider.java
new file mode 100644 (file)
index 0000000..6945bda
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+* Copyright (C) 2014 The CyanogenMod 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.cyanogenmod.filemanager.providers;
+
+import android.content.ContentProvider;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+
+import static android.content.UriMatcher.NO_MATCH;
+
+/**
+ * MimeTypeIndexProvider
+ * <pre>
+ *     Provider for handling access of mime type indexes
+ * </pre>
+ *
+ * @see {@link android.content.ContentProvider}
+ */
+public class MimeTypeIndexProvider extends ContentProvider {
+
+    // Constants
+    private static final String TAG = MimeTypeIndexProvider.class.getSimpleName();
+    private static final String AUTHORITY = "com.cyanogenmod.filemanager.providers.index";
+    private static final int ID_INDEX = 1;
+    private static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
+            DatabaseHelper.INDEX_TABLE);
+
+    private static final UriMatcher sUriMatcher = new UriMatcher(NO_MATCH);
+    public static final String COLUMN_FILE_ROOT = "file_root";
+    public static final String COLUMN_CATEGORY = "category";
+    public static final String COLUMN_SIZE = "size";
+
+    public static Uri getContentUri() {
+        return new Uri.Builder().scheme("content").authority(AUTHORITY).path
+                (DatabaseHelper.INDEX_TABLE).build();
+    }
+
+    static {
+        sUriMatcher.addURI(AUTHORITY, DatabaseHelper.INDEX_TABLE, ID_INDEX);
+    }
+
+    private SQLiteDatabase mSQLiteDatabase;
+
+    @Override
+    public boolean onCreate() {
+        DatabaseHelper dbHelper = new DatabaseHelper(getContext());
+        mSQLiteDatabase = dbHelper.getWritableDatabase();
+        return mSQLiteDatabase != null;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        String tableName = null;
+        switch (sUriMatcher.match(uri)) {
+            case ID_INDEX:
+                tableName = DatabaseHelper.INDEX_TABLE;
+                break;
+            default:
+                throw new RuntimeException("URI not supported!");
+        }
+
+        Cursor cursor = mSQLiteDatabase.query(tableName, projection, selection, selectionArgs, null, null, sortOrder);
+        if (cursor != null) {
+            cursor.setNotificationUri(getContext().getContentResolver(), uri);
+        }
+        return cursor;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return null;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues contentValues) {
+        String tableName = null;
+        switch (sUriMatcher.match(uri)) {
+            case ID_INDEX:
+                tableName = DatabaseHelper.INDEX_TABLE;
+                break;
+            default:
+                throw new RuntimeException("URI not supported!");
+        }
+        long rowId = mSQLiteDatabase.insert(tableName, null, contentValues);
+        if (rowId > 0) {
+            Uri newUri = ContentUris.withAppendedId(CONTENT_URI, rowId);
+            getContext().getContentResolver().notifyChange(newUri, null);
+            return newUri;
+        }
+        throw new SQLException("Failed to add new record");
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        String tableName = null;
+        switch (sUriMatcher.match(uri)) {
+            case ID_INDEX:
+                tableName = DatabaseHelper.INDEX_TABLE;
+                break;
+            default:
+                throw new RuntimeException("URI not supported!");
+        }
+        int count = mSQLiteDatabase.delete(tableName, selection, selectionArgs);
+        getContext().getContentResolver().notifyChange(uri, null);
+        return count;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues contentValues, String selection, String[]
+            selectionArgs) {
+        throw new RuntimeException("MimeTypeIndexProvider::update(): Not implemented!");
+    }
+
+    private static class DatabaseHelper extends SQLiteOpenHelper {
+
+        // Constants
+        static final int DATABASE_VERSION = 1;
+        static final String DATABASE_NAME = "mime_type_index";
+        static final String INDEX_TABLE = "type_index";
+        static final String CREATE_INDEX_TABLE = "CREATE TABLE IF NOT EXISTS " +
+                INDEX_TABLE +
+                " ( " +
+                "`_id` INTEGER PRIMARY KEY AUTOINCREMENT, " +
+                "`" + COLUMN_FILE_ROOT + "` TEXT, " +
+                "`" + COLUMN_CATEGORY + "` TEXT, " +
+                "`" + COLUMN_SIZE + "` INTEGER " +
+                ")";
+
+        /**
+         * Constructor
+         *
+         * @param context {@link android.content.Context}
+         */
+        public DatabaseHelper(Context context) {
+            super(context, DATABASE_NAME, null, DATABASE_VERSION);
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase sqLiteDatabase) {
+            sqLiteDatabase.execSQL(CREATE_INDEX_TABLE);
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
+            sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + INDEX_TABLE);
+            onCreate(sqLiteDatabase);
+        }
+    }
+
+    /**
+     * Get the mount point usage data via sql cursor
+     *
+     * @param context  {@link android.content.Context} not null
+     * @param fileRoot {@link java.lang.String} not null or empty
+     *
+     * @return {@link android.database.Cursor}
+     *
+     * @throws IllegalArgumentException {@link java.lang.IllegalArgumentException}
+     */
+    public static Cursor getMountPointUsage(Context context, String fileRoot) throws
+            IllegalArgumentException {
+        if (context == null) {
+            throw new IllegalArgumentException("'context' cannot be null!");
+        }
+        if (TextUtils.isEmpty(fileRoot)) {
+            throw new IllegalArgumentException("'fileRoot' cannot be null or empty!");
+        }
+        String selection = COLUMN_FILE_ROOT + " = ?";
+        String[] selectionArgs = new String[] { fileRoot };
+        return context.getContentResolver().query(MimeTypeIndexProvider.getContentUri(),
+                null, selection, selectionArgs, null);
+    }
+
+    /**
+     * Clear the mount point usage data for the file root
+     *
+     * @param context  {@link android.content.Context} not null
+     * @param fileRoot {@link java.lang.String} not null or empty
+     *
+     * @return {@link java.lang.Integer}
+     *
+     * @throws IllegalArgumentException {@link java.lang.IllegalArgumentException}
+     */
+    public static int clearMountPointUsages(Context context, String fileRoot)  throws
+            IllegalArgumentException {
+        if (context == null) {
+            throw new IllegalArgumentException("'context' cannot be null!");
+        }
+        if (TextUtils.isEmpty(fileRoot)) {
+            throw new IllegalArgumentException("'fileRoot' cannot be null or empty!");
+        }
+                String selection = COLUMN_FILE_ROOT + " = ?";
+        String[] selectionArgs = new String[] { fileRoot };
+        return context.getContentResolver().delete(MimeTypeIndexProvider.getContentUri(), selection, selectionArgs);
+    }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/service/MimeTypeIndexService.java b/src/com/cyanogenmod/filemanager/service/MimeTypeIndexService.java
new file mode 100644 (file)
index 0000000..7f92692
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+* Copyright (C) 2014 The CyanogenMod 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.cyanogenmod.filemanager.service;
+
+import android.app.IntentService;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.text.TextUtils;
+import android.util.Log;
+import com.cyanogenmod.filemanager.providers.MimeTypeIndexProvider;
+import com.cyanogenmod.filemanager.util.MimeTypeHelper;
+import com.cyanogenmod.filemanager.util.MimeTypeHelper.MimeTypeCategory;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * MimeTypeIndexService
+ * <pre>
+ *    Service intended to index space used by mime type
+ * </pre>
+ *
+ * @see {@link android.app.IntentService}
+ */
+public class MimeTypeIndexService extends IntentService {
+
+    // Constants
+    private static final String TAG = MimeTypeIndexService.class.getSimpleName();
+    public static final String ACTION_START_INDEX = "com.cyanogenmod.filemanager" +
+            ".ACTION_START_INDEX";
+    public static final String EXTRA_FILE_ROOT = "extra_file_root";
+
+    /**
+     * Constructor
+     */
+    public MimeTypeIndexService() {
+        super(TAG);
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        Log.v(TAG, "onHandleIntent(" + intent + ")");
+        if (intent == null) {
+            Log.w(TAG, "Intent passed was null");
+            return;
+        }
+        String action = intent.getAction();
+        Log.d(TAG, "Action: " + action);
+        if (TextUtils.isEmpty(action)) {
+            Log.w(TAG, "Failed to parse action");
+            return;
+        }
+        String fileRoot = intent.getStringExtra(EXTRA_FILE_ROOT);
+        if (TextUtils.isEmpty(fileRoot)) {
+            Log.w(TAG, "Empty file root, bailing out");
+            return;
+        }
+        if (ACTION_START_INDEX.equalsIgnoreCase(action)) {
+            performIndexAction(fileRoot);
+        }
+    }
+
+    private void performIndexAction(String fileRoot) {
+        Log.v(TAG, "performIndexAction(" + fileRoot + ")");
+        if (TextUtils.isEmpty(fileRoot)) {
+            Log.w(TAG, "Empty or null file root '" + fileRoot + "'");
+            return;
+        }
+        Log.i(TAG, "Starting mime type usage indexing on '" + fileRoot + "'");
+        fileRoot = fileRoot.trim();
+        File rootFile = new File(fileRoot);
+        Map<MimeTypeCategory, Long> spaceCalculationMap =
+                new HashMap<MimeTypeCategory, Long>();
+        calculateUsageByType(rootFile, spaceCalculationMap);
+        ContentValues[] valuesList = new ContentValues[spaceCalculationMap.keySet().size()];
+        int i = 0;
+        for (MimeTypeCategory category : spaceCalculationMap.keySet()) {
+            Log.d(TAG, "" + category + " = " + spaceCalculationMap.get(category));
+            ContentValues values = new ContentValues();
+            values.put(MimeTypeIndexProvider.COLUMN_FILE_ROOT, fileRoot);
+            values.put(MimeTypeIndexProvider.COLUMN_CATEGORY, category.name());
+            values.put(MimeTypeIndexProvider.COLUMN_SIZE, spaceCalculationMap.get(category));
+            valuesList[i] = values;
+            i++;
+        }
+        MimeTypeIndexProvider.clearMountPointUsages(this, fileRoot); // Clear old data
+        getContentResolver().bulkInsert(MimeTypeIndexProvider.getContentUri(), valuesList);
+    }
+
+    private class FileOnlyFileFilter implements FileFilter {
+        @Override
+        public boolean accept(File file) {
+            return file != null && !file.isDirectory() && file.isFile();
+        }
+    }
+
+    private class DirectoryOnlyFileFilter implements FileFilter {
+        @Override
+        public boolean accept(File file) {
+            return file != null && file.isDirectory();
+        }
+    }
+
+    private void calculateUsageByType(File root, Map<MimeTypeCategory, Long> groupUsageMap) {
+        File[] dirs = root.listFiles(new DirectoryOnlyFileFilter());
+        File[] files = root.listFiles(new FileOnlyFileFilter());
+        if (dirs != null) {
+            // Recurse directories
+            for (File dir : dirs) {
+                calculateUsageByType(dir, groupUsageMap);
+            }
+        }
+        if (files != null) {
+            // Iterate every file
+            for (File file : files) {
+                MimeTypeCategory category = MimeTypeHelper.getCategory(this, file);
+                long size = file.length();
+                if (!groupUsageMap.containsKey(category)) {
+                    groupUsageMap.put(category, size);
+                } else {
+                    long newSum = groupUsageMap.get(category) + size;
+                    groupUsageMap.put(category, newSum);
+                }
+            }
+        }
+    }
+
+    /**
+     * Kick off an indexing job for the provided file root or mount point root
+     *
+     * @param context  {@link android.content.Context}
+     * @param fileRoot {@link java.lang.String}
+     *
+     * @throws IllegalArgumentException {@link java.lang.IllegalArgumentException}
+     */
+    public static void indexFileRoot(Context context, String fileRoot) throws
+            IllegalArgumentException {
+        if (context == null) {
+            throw new IllegalArgumentException("'context' cannot be null");
+        }
+        // Start indexing the external storage
+        Intent intent = new Intent(context, MimeTypeIndexService.class);
+        intent.setAction(MimeTypeIndexService.ACTION_START_INDEX);
+        intent.putExtra(MimeTypeIndexService.EXTRA_FILE_ROOT, fileRoot);
+        context.startService(intent);
+    }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/tasks/FetchStatsByTypeTask.java b/src/com/cyanogenmod/filemanager/tasks/FetchStatsByTypeTask.java
new file mode 100644 (file)
index 0000000..de9cfc6
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+* Copyright (C) 2014 The CyanogenMod 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.cyanogenmod.filemanager.tasks;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.os.AsyncTask;
+import com.cyanogenmod.filemanager.providers.MimeTypeIndexProvider;
+
+/**
+ * FetchStatsByTypeTask
+ * <pre>
+ *     Task for fetching the cursor of data for stats by mime type
+ * </pre>
+ *
+ * @see {@link android.os.AsyncTask}
+ */
+public class FetchStatsByTypeTask extends AsyncTask<String, Void, Cursor> {
+
+    // Members
+    private Context mContext;
+    private Listener mListener;
+
+    /**
+     * Constructor
+     *
+     * @param context  {@link android.content.Context}
+     * @param listener {@link com.cyanogenmod.filemanager.tasks.FetchStatsByTypeTask.Listener}
+     *
+     * @throws IllegalArgumentException {@link java.lang.IllegalArgumentException}
+     */
+    public FetchStatsByTypeTask(Context context, Listener listener)
+            throws IllegalArgumentException {
+        if (context == null) {
+            throw new IllegalArgumentException("'context' cannot be null");
+        }
+        mContext = context;
+        mListener = listener;
+    }
+
+    @Override
+    protected Cursor doInBackground(String... strings) {
+        if (strings.length < 1) {
+            return null;
+        }
+        String fileRoot = strings[0];
+        return MimeTypeIndexProvider.getMountPointUsage(mContext, fileRoot);
+    }
+
+    @Override
+    protected void onPostExecute(Cursor cursor) {
+        if (mListener != null) {
+            mListener.onCursor(cursor);
+        }
+    }
+
+    /**
+     * Callback interface for this task
+     */
+    public interface Listener {
+        public void onCursor(Cursor cursor);
+    }
+
+}
index 392f36f..5ead319 100644 (file)
@@ -19,37 +19,128 @@ package com.cyanogenmod.filemanager.ui.dialogs;
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.database.Cursor;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.CompoundButton;
 import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.GridLayout;
 import android.widget.Switch;
 import android.widget.TextView;
-
 import com.cyanogenmod.filemanager.FileManagerApplication;
 import com.cyanogenmod.filemanager.R;
 import com.cyanogenmod.filemanager.console.Console;
 import com.cyanogenmod.filemanager.console.ConsoleBuilder;
 import com.cyanogenmod.filemanager.model.DiskUsage;
+import com.cyanogenmod.filemanager.model.DiskUsageCategory;
 import com.cyanogenmod.filemanager.model.MountPoint;
 import com.cyanogenmod.filemanager.preferences.AccessMode;
 import com.cyanogenmod.filemanager.preferences.FileManagerSettings;
 import com.cyanogenmod.filemanager.preferences.Preferences;
+import com.cyanogenmod.filemanager.providers.MimeTypeIndexProvider;
+import com.cyanogenmod.filemanager.tasks.FetchStatsByTypeTask;
+import com.cyanogenmod.filemanager.tasks.FetchStatsByTypeTask.Listener;
 import com.cyanogenmod.filemanager.ui.ThemeManager;
 import com.cyanogenmod.filemanager.ui.ThemeManager.Theme;
 import com.cyanogenmod.filemanager.ui.widgets.DiskUsageGraph;
 import com.cyanogenmod.filemanager.util.CommandHelper;
 import com.cyanogenmod.filemanager.util.DialogHelper;
 import com.cyanogenmod.filemanager.util.FileHelper;
+import com.cyanogenmod.filemanager.util.MimeTypeHelper.MimeTypeCategory;
 import com.cyanogenmod.filemanager.util.MountPointHelper;
 
 /**
  * A class that wraps a dialog for showing information about a mount point.<br />
  * This class display information like mount point name, device name, size, type, ...
  */
-public class FilesystemInfoDialog implements OnClickListener, OnCheckedChangeListener {
+public class FilesystemInfoDialog implements OnClickListener, OnCheckedChangeListener, Listener {
+
+    @Override
+    public void onCursor(Cursor cursor) {
+        if (cursor == null) {
+            Log.w(TAG, "Cursor is null");
+            return;
+        }
+        if (mDiskUsage == null) {
+            Log.w(TAG, "No disk usage available!");
+            return;
+        }
+        mDiskUsage.clearUsageCategories();
+        while(cursor.moveToNext()) {
+            String fileRoot = cursor.getString(cursor.getColumnIndex(MimeTypeIndexProvider
+                    .COLUMN_FILE_ROOT));
+            String categoryString = cursor.getString(cursor.getColumnIndex(MimeTypeIndexProvider
+                    .COLUMN_CATEGORY));
+            long size = cursor.getLong(cursor.getColumnIndex(MimeTypeIndexProvider.COLUMN_SIZE));
+            MimeTypeCategory category = MimeTypeCategory.valueOf(categoryString);
+            DiskUsageCategory usageCategory = new DiskUsageCategory(category, size);
+            mDiskUsage.addUsageCategory(usageCategory);
+
+            // [TODO][MSB]: Unhandled case: No data, sync in progress, ready to draw
+            // * Should check if 0 length, then wait on uri notification?
+            // ** This should only happen if you are using it without a sync ever having happened
+            // ** before.
+            // * Should always wait on uri notification and update drawing?
+            // * Otherwise if we have data then we are good to go!
+            // * Also should think of a way to dispatch and index refresh (alarm manager? file
+            // ** observer?)
+
+        }
+
+        this.mDiskUsageGraph.post(new Runnable() {
+            @Override
+            public void run() {
+                //Animate disk usage graph
+                FilesystemInfoDialog.this.mDiskUsageGraph.drawDiskUsage(mDiskUsage);
+                if (mIsInUsageTab) {
+                    if (mLegendLayout.getVisibility() != View.VISIBLE) {
+                        populateLegend();
+                        mLegendLayout.setVisibility(View.VISIBLE);
+                    }
+                }
+                isFetching = false;
+            }
+        });
+    }
+
+    private void populateLegend() {
+        if (mLegendLayout == null) {
+            Log.w(TAG, "Unable to find view for legend");
+            return;
+        }
+        mLegendLayout.removeAllViews();
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+        int index = 0;
+        if (mDiskUsage == null) {
+            Log.w(TAG, "No disk usage information");
+            return;
+        }
+        for (DiskUsageCategory category : mDiskUsage.getUsageCategoryList()) {
+            View ll = inflater.inflate(R.layout.disk_usage_category_view, null, false);
+            View colorView = ll.findViewById(R.id.v_legend_swatch);
+            index = (index < DiskUsageGraph.COLOR_LIST.size()) ? index : 0; // normalize index
+            colorView.setBackgroundColor(DiskUsageGraph.COLOR_LIST.get(index));
+            TextView titleView = (TextView) ll.findViewById(R.id.tv_legend_title);
+            String localizedName = MimeTypeCategory.getFriendlyLocalizedNames(mContext)[category
+                    .getCategory().ordinal()];
+            titleView.setText(localizedName);
+            mLegendLayout.addView(ll);
+            index++;
+        }
+    }
+
+    boolean isFetching;
+    FetchStatsByTypeTask mFetchStatsByTypeTask;
+    private void fetchStats(String fileRoot) {
+        if (!isFetching) {
+            isFetching = true;
+            mFetchStatsByTypeTask.execute(fileRoot);
+        } else {
+            Log.w(TAG, "Already fetching data...");
+        }
+    }
 
     /**
      * An interface to communicate when the user change the mount state
@@ -87,11 +178,13 @@ public class FilesystemInfoDialog implements OnClickListener, OnCheckedChangeLis
      */
     DiskUsageGraph mDiskUsageGraph;
     private TextView mInfoMsgView;
+    private GridLayout mLegendLayout;
 
     private OnMountListener mOnMountListener;
 
     private boolean mIsMountAllowed;
     private final boolean mIsAdvancedMode;
+    private boolean mIsInUsageTab = false;
 
     /**
      * Constructor of <code>FilesystemInfoDialog</code>.
@@ -108,6 +201,8 @@ public class FilesystemInfoDialog implements OnClickListener, OnCheckedChangeLis
 
         //Save data
         this.mMountPoint = mountPoint;
+        mFetchStatsByTypeTask = new FetchStatsByTypeTask(this.mContext, this);
+        fetchStats(mMountPoint.getMountPoint());
         this.mDiskUsage = diskUsage;
         this.mIsMountAllowed = false;
         this.mIsAdvancedMode =
@@ -168,6 +263,8 @@ public class FilesystemInfoDialog implements OnClickListener, OnCheckedChangeLis
         this.mDiskUsageGraph =
                 (DiskUsageGraph)contentView.findViewById(R.id.filesystem_disk_usage_graph);
 
+        this.mLegendLayout = (GridLayout) contentView.findViewById(R.id.ll_legend);
+
         // Set the user preference about free disk space warning level
         String fds = Preferences.getSharedPreferences().getString(
                 FileManagerSettings.SETTINGS_DISK_USAGE_WARNING_LEVEL.getId(),
@@ -255,6 +352,7 @@ public class FilesystemInfoDialog implements OnClickListener, OnCheckedChangeLis
     public void onClick(View v) {
         switch (v.getId()) {
             case R.id.filesystem_info_dialog_tab_info:
+                mIsInUsageTab = false;
                 if (!this.mInfoViewTab.isSelected()) {
                     this.mInfoViewTab.setSelected(true);
                     ((TextView)this.mInfoViewTab).setTextAppearance(
@@ -270,9 +368,11 @@ public class FilesystemInfoDialog implements OnClickListener, OnCheckedChangeLis
                 }
                 this.mInfoMsgView.setVisibility(
                         this.mIsMountAllowed || !this.mIsAdvancedMode ? View.GONE : View.VISIBLE);
+                mLegendLayout.setVisibility(View.INVISIBLE);
                 break;
 
             case R.id.filesystem_info_dialog_tab_disk_usage:
+                mIsInUsageTab = true;
                 if (!this.mDiskUsageViewTab.isSelected()) {
                     this.mInfoViewTab.setSelected(false);
                     ((TextView)this.mInfoViewTab).setTextAppearance(
@@ -290,13 +390,19 @@ public class FilesystemInfoDialog implements OnClickListener, OnCheckedChangeLis
                     @Override
                     public void run() {
                         //Animate disk usage graph
-                        FilesystemInfoDialog.this.mDiskUsageGraph.drawDiskUsage(
-                                FilesystemInfoDialog.this.mDiskUsage);
+                        FilesystemInfoDialog.this.mDiskUsageGraph.drawDiskUsage(mDiskUsage);
+                        if (mIsInUsageTab) {
+                            if (mLegendLayout.getVisibility() != View.VISIBLE) {
+                                populateLegend();
+                                mLegendLayout.setVisibility(View.VISIBLE);
+                            }
+                        }
                     }
                 });
                 break;
 
             case R.id.filesystem_info_msg:
+                mIsInUsageTab = false;
                 //Change the console
                 boolean superuser = ConsoleBuilder.changeToPrivilegedConsole(this.mContext);
                 if (superuser) {
@@ -319,9 +425,12 @@ public class FilesystemInfoDialog implements OnClickListener, OnCheckedChangeLis
                     this.mInfoMsgView.setVisibility(View.VISIBLE);
                     this.mIsMountAllowed = false;
                 }
+                mLegendLayout.setVisibility(View.INVISIBLE);
                 break;
 
             default:
+                mIsInUsageTab = false;
+                mLegendLayout.setVisibility(View.INVISIBLE);
                 break;
         }
     }
index ecd0cfa..bdae373 100644 (file)
 package com.cyanogenmod.filemanager.ui.widgets;
 
 import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.RectF;
+import android.graphics.*;
 import android.util.AttributeSet;
 import android.view.View;
-
+import android.widget.Toast;
+import com.cyanogenmod.filemanager.R;
 import com.cyanogenmod.filemanager.model.DiskUsage;
+import com.cyanogenmod.filemanager.model.DiskUsageCategory;
 import com.cyanogenmod.filemanager.ui.ThemeManager;
 import com.cyanogenmod.filemanager.ui.ThemeManager.Theme;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 
 /**
  * A class that display graphically the usage of a mount point.
@@ -38,11 +40,62 @@ import java.util.List;
 public class DiskUsageGraph extends View {
 
     /**
+     * This is a list for accessing the loaded colors
+     */
+    public static final List<Integer> COLOR_LIST = new ArrayList<Integer>();
+    /**
+     * This is an internal color id reference
+     */
+    private static final List<Integer> INTERNAL_COLOR_LIST = new ArrayList<Integer>() {
+        {
+
+            // Material Blue
+            add(R.color.material_palette_blue_1);
+            add(R.color.material_palette_blue_2);
+            add(R.color.material_palette_blue_3);
+            add(R.color.material_palette_blue_4);
+
+            // Material Lime
+            add(R.color.material_palette_green_1);
+            add(R.color.material_palette_green_2);
+            add(R.color.material_palette_green_3);
+            add(R.color.material_palette_green_4);
+
+            // Material Orange
+            add(R.color.material_palette_orange_1);
+            add(R.color.material_palette_orange_2);
+            add(R.color.material_palette_orange_3);
+            add(R.color.material_palette_orange_4);
+
+            // Material Pink
+            add(R.color.material_palette_pink_1);
+            add(R.color.material_palette_pink_2);
+            add(R.color.material_palette_pink_3);
+            add(R.color.material_palette_pink_4);
+
+
+        }
+    };
+
+    /**
+     * Initialize the color assets into memory for direct access
+     */
+    private void initializeColors() {
+        // Only load the colors if needed
+        if (COLOR_LIST.size() == 0) {
+            for (int colorId : INTERNAL_COLOR_LIST) {
+                COLOR_LIST.add(getContext().getResources().getColor(colorId));
+            }
+        }
+    }
+
+    /**
      * @hide
      */
     int mDiskWarningAngle = (360 * 95) / 100;
 
-    private AnimationDrawingThread mThread;
+    private static String sWarningText;
+
     /**
      * @hide
      */
@@ -50,12 +103,18 @@ public class DiskUsageGraph extends View {
             Collections.synchronizedList(new ArrayList<DiskUsageGraph.DrawingObject>(2));
 
     /**
+     * @hide
+     * drawing objects lock
+     */
+    static final int[] LOCK = new int[0];
+
+    /**
      * Constructor of <code>DiskUsageGraph</code>.
      *
      * @param context The current context
      */
     public DiskUsageGraph(Context context) {
-        super(context);
+        this(context, null);
     }
 
     /**
@@ -65,7 +124,7 @@ public class DiskUsageGraph extends View {
      * @param attrs The attributes of the XML tag that is inflating the view.
      */
     public DiskUsageGraph(Context context, AttributeSet attrs) {
-        super(context, attrs);
+        this(context, attrs, 0);
     }
 
     /**
@@ -80,6 +139,10 @@ public class DiskUsageGraph extends View {
      */
     public DiskUsageGraph(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+        initializeColors();
+        if (sWarningText == null) {
+            sWarningText = context.getResources().getString(R.string.pref_disk_usage_warning_level);
+        }
     }
 
     /**
@@ -94,8 +157,8 @@ public class DiskUsageGraph extends View {
     }
 
     /**
-     * Method that sets the free disk space percentage after the widget change his color
-     * to advise the user
+     * Method that sets the free disk space percentage after the widget change his color to advise
+     * the user
      *
      * @param percentage The free disk space percentage
      */
@@ -103,22 +166,32 @@ public class DiskUsageGraph extends View {
         this.mDiskWarningAngle = (360 * percentage) / 100;
     }
 
+    // Handle thread for drawing calculations
+    private Future mAnimationFuture = null;
+    private static ExecutorService sThreadPool = Executors.newFixedThreadPool(1);
+
     /**
      * Method that draw the disk usage.
      *
-     * @param diskUsage The disk usage
+     * @param diskUsage {@link com.cyanogenmod.filemanager.model.DiskUsage} The disk usage params
      */
     public void drawDiskUsage(DiskUsage diskUsage) {
+
         // Clear if a current drawing exit
-        if (this.mThread != null) {
-            this.mThread.exit();
+        if (mAnimationFuture != null && !mAnimationFuture.isCancelled()) {
+            mAnimationFuture.cancel(true);
+        }
+
+        // Clear canvas
+        synchronized (LOCK) {
+            this.mDrawingObjects.clear();
         }
-        this.mDrawingObjects.clear();
         invalidate();
 
         // Start drawing thread
-        this.mThread = new AnimationDrawingThread(diskUsage);
-        this.mThread.start();
+        AnimationDrawingRunnable animationDrawingRunnable = new AnimationDrawingRunnable(diskUsage);
+        mAnimationFuture = sThreadPool.submit(animationDrawingRunnable);
+
     }
 
     /**
@@ -130,32 +203,116 @@ public class DiskUsageGraph extends View {
         super.onDraw(canvas);
 
         //Draw all the drawing objects
-        int cc = this.mDrawingObjects.size();
-        for (int i = 0; i < cc; i++) {
-            DrawingObject dwo = this.mDrawingObjects.get(i);
-            canvas.drawArc(dwo.mRectF, dwo.mStartAngle, dwo.mSweepAngle, false, dwo.mPaint);
+        synchronized (LOCK) {
+            for (DrawingObject dwo : this.mDrawingObjects) {
+                canvas.drawArc(dwo.mRectF, dwo.mStartAngle, dwo.mSweepAngle, false, dwo.mPaint);
+            }
         }
     }
 
     /**
      * A thread for drawing the animation of the graph.
      */
-    private class AnimationDrawingThread extends Thread {
+    private class AnimationDrawingRunnable implements Runnable {
 
         private final DiskUsage mDiskUsage;
-        private boolean mRunning;
-        private final Object mSync = new Object();
-        private int mIndex = 0;
+
+        // Delay in between UI updates and slow down calculations
+        private static final long ANIMATION_DELAY = 1l;
+
+        // Slop space adjustment for space between segments
+        private static final int SLOP = 2;
+
+        // flags
+        private static final boolean USE_COLORS = true;
 
         /**
          * Constructor of <code>AnimationDrawingThread</code>.
          *
          * @param diskUsage The disk usage
          */
-        public AnimationDrawingThread(DiskUsage diskUsage) {
-            super();
+        public AnimationDrawingRunnable(DiskUsage diskUsage) {
             this.mDiskUsage = diskUsage;
-            this.mRunning = false;
+        }
+
+        private void sleepyTime() {
+            try {
+                Thread.sleep(ANIMATION_DELAY);
+            } catch (InterruptedException ignored) {
+            }
+        }
+
+        private void redrawCanvas() {
+            //Redraw the canvas
+            post(new Runnable() {
+                @Override
+                public void run() {
+                    invalidate();
+                }
+            });
+        }
+
+        private void drawTotal(Rect rect, int stroke) {
+            // Draw total
+            DrawingObject drawingObject = createDrawingObject(rect, "disk_usage_total_color",
+                    stroke);
+            synchronized (LOCK) {
+                mDrawingObjects.add(drawingObject);
+            }
+            while (drawingObject.mSweepAngle < 360) {
+                drawingObject.mSweepAngle++;
+                redrawCanvas();
+                sleepyTime();
+            }
+        }
+
+        private void drawUsed(Rect rect, int stroke, float used) {
+            // Draw used
+            DrawingObject drawingObject = createDrawingObject(rect, "disk_usage_used_color", stroke);
+            synchronized (LOCK) {
+                mDrawingObjects.add(drawingObject);
+            }
+            while (drawingObject.mSweepAngle < used) {
+                drawingObject.mSweepAngle++;
+                redrawCanvas();
+                sleepyTime();
+            }
+        }
+
+        private void drawUsedWithColors(Rect rect, int stroke) {
+            // Draw used segments
+            if (mDiskUsage != null) {
+                int lastSweepAngle = 0;
+                float catUsed = 100.0f;
+                int color;
+                int index = 0;
+                for (DiskUsageCategory category : mDiskUsage.getUsageCategoryList()) {
+                    catUsed = (category.getSizeBytes() * 100) / mDiskUsage.getTotal(); // calc percent
+                    catUsed = (catUsed < 1) ? 1 : catUsed; // Normalize
+                    catUsed = (360 * catUsed) / 100; // calc angle
+
+                    // Figure out a color
+                    if (index > -1 && index < COLOR_LIST.size()) {
+                        color = COLOR_LIST.get(index);
+                        index++;
+                    } else {
+                        index = 0;
+                        color = COLOR_LIST.get(index);
+                    }
+
+                    DrawingObject drawingObject = createDrawingObjectNoTheme(rect, color, stroke);
+                     drawingObject.mStartAngle += lastSweepAngle;
+                    synchronized (LOCK) {
+                        mDrawingObjects.add(drawingObject);
+                    }
+                    while (drawingObject.mSweepAngle < catUsed + SLOP) {
+                        drawingObject.mSweepAngle++;
+                        redrawCanvas();
+                        sleepyTime();
+                    }
+                    lastSweepAngle += drawingObject.mSweepAngle - SLOP;
+                }
+            }
         }
 
         /**
@@ -172,108 +329,27 @@ public class DiskUsageGraph extends View {
             rect.top += stroke / 2;
             rect.bottom -= stroke / 2;
 
-            float used = 0.0f;
-            if (this.mDiskUsage == null) {
-                used = 100.0f;
-            } else if (this.mDiskUsage.getTotal() != 0) {
+            float used = 100.0f;
+            if (this.mDiskUsage != null && this.mDiskUsage.getTotal() != 0) {
                 used = (this.mDiskUsage.getUsed() * 100) / this.mDiskUsage.getTotal();
             }
             //Translate to angle
             used = (360 * used) / 100;
 
-            synchronized (this.mSync) {
-                this.mRunning = true;
-            }
-            try {
-                boolean disk_warning = false;
-                while (this.mRunning) {
-                    //Get the current arc
-                    DrawingObject dwo = null;
-                    if (DiskUsageGraph.this.mDrawingObjects != null
-                            && DiskUsageGraph.this.mDrawingObjects.size() > this.mIndex) {
-                        dwo = DiskUsageGraph.this.mDrawingObjects.get(this.mIndex);
-                    }
-
-                    //Draw the total arc circle and then the used arc circle
-                    if (this.mIndex == 0 && dwo == null) {
-                        //Initialize the total arc circle
-                        DiskUsageGraph.this.mDrawingObjects.add(
-                                createDrawingObject(
-                                        rect, "disk_usage_total_color", stroke)); //$NON-NLS-1$
-                        continue;
-                    }
-                    if (this.mIndex == 1 && dwo == null) {
-                      //Initialize the used arc circle
-                        DiskUsageGraph.this.mDrawingObjects.add(
-                                createDrawingObject(
-                                        rect, "disk_usage_used_color", stroke)); //$NON-NLS-1$
-                        continue;
-                    }
+            // Draws out the graph background color
+            drawTotal(rect, stroke);
 
-                    if (this.mIndex == 1 && !disk_warning &&
-                            dwo.mSweepAngle >= DiskUsageGraph.this.mDiskWarningAngle) {
-                        Theme theme = ThemeManager.getCurrentTheme(getContext());
-                        dwo.mPaint.setColor(
-                                theme.getColor(
-                                        getContext(),
-                                        "disk_usage_used_warning_color")); //$NON-NLS-1$
-                        disk_warning = true;
-                    }
-
-                    //Redraw the canvas
-                    post(new Runnable() {
-                        @Override
-                        public void run() {
-                            invalidate();
-                        }
-                    });
-
-                    //Next draw call
-                    dwo.mSweepAngle++;
-                    if (this.mIndex >= 1) {
-                        //Only fill until used
-                        if ((dwo.mSweepAngle >= used) || (this.mIndex > 1)) {
-                            synchronized (this.mSync) {
-                                break;  //End of the animation
-                            }
-                        }
-                    }
-                    if (dwo.mSweepAngle == 360) {
-                        this.mIndex++;
-                    }
-
-                    try {
-                        Thread.sleep(1L);
-                    } catch (Throwable ex) {
-                        /**NON BLOCK**/
-                    }
-                }
-            } finally {
-                try {
-                    synchronized (this.mSync) {
-                        this.mRunning = false;
-                        this.mSync.notify();
-                    }
-                } catch (Throwable ex) {
-                    /**NON BLOCK**/
-                }
+            // Draw the usage
+            if (USE_COLORS) {
+                drawUsedWithColors(rect, stroke);
+            } else {
+                drawUsed(rect, stroke, used);
             }
-        }
 
-        /**
-         * Method that force the thread to exit.
-         */
-        public void exit() {
-            try {
-                synchronized (this.mSync) {
-                    if (this.mRunning) {
-                        this.mRunning = false;
-                        this.mSync.wait();
-                    }
-                }
-            } catch (Throwable ex) {
-                /**NON BLOCK**/
+            if (used >= mDiskWarningAngle) {
+                Toast.makeText(getContext(), sWarningText, Toast.LENGTH_SHORT).show();
             }
+
         }
 
         /**
@@ -282,6 +358,7 @@ public class DiskUsageGraph extends View {
          * @param rect The area of drawing
          * @param colorResourceThemeId The theme resource identifier of the color
          * @param stroke The stroke width
+         *
          * @return DrawingObject The drawing object
          */
         private DrawingObject createDrawingObject(
@@ -297,6 +374,31 @@ public class DiskUsageGraph extends View {
             out.mRectF = new RectF(rect);
             return out;
         }
+
+        /**
+         * Method that creates the drawing object.
+         *
+         * @param rect The area of drawing
+         * @param color Integer id of the color
+         * @param stroke The stroke width
+         *
+         * @return DrawingObject The drawing object
+         *
+         * [TODO][MSB]: Implement colors for sections into theme
+         */
+        @Deprecated
+        private DrawingObject createDrawingObjectNoTheme(
+                Rect rect, int color, int stroke) {
+            DrawingObject out = new DrawingObject();
+            out.mSweepAngle = 0;
+            out.mPaint.setColor(color);
+            out.mPaint.setStrokeWidth(stroke);
+            out.mPaint.setAntiAlias(true);
+            out.mPaint.setStrokeCap(Paint.Cap.BUTT);
+            out.mPaint.setStyle(Paint.Style.STROKE);
+            out.mRectF = new RectF(rect);
+            return out;
+        }
     }
 
     /**
@@ -304,6 +406,7 @@ public class DiskUsageGraph extends View {
      */
     private class DrawingObject {
         DrawingObject() {/**NON BLOCK**/}
+
         int mStartAngle = -180;
         int mSweepAngle = 0;
         Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);