aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Brabham <optedoblivion@cyngn.com>2014-12-15 10:28:45 -0800
committerMartin Brabham <optedoblivion@cyngn.com>2014-12-16 11:51:44 -0800
commit486c19adee31fac87ef5a70fe37611e64aea99d1 (patch)
tree73d0139b57d74204b0673288113750563ac09e8a
parent3a587806258e5d39f4dcaea2220a3b5bd590b0d4 (diff)
downloadandroid_packages_apps_CMFileManager-486c19adee31fac87ef5a70fe37611e64aea99d1.tar.gz
android_packages_apps_CMFileManager-486c19adee31fac87ef5a70fe37611e64aea99d1.tar.bz2
android_packages_apps_CMFileManager-486c19adee31fac87ef5a70fe37611e64aea99d1.zip
CMFileManager: add usage stats by mime type
Change-Id: I92ffb45ec3ef0dc6feb8b8129bcb0fedf442ba54
-rw-r--r--AndroidManifest.xml12
-rw-r--r--res/layout/disk_usage_category_view.xml38
-rw-r--r--res/layout/filesystem_info_dialog.xml15
-rw-r--r--res/values/colors.xml26
-rw-r--r--res/values/dimen.xml6
-rw-r--r--src/com/cyanogenmod/filemanager/FileManagerApplication.java9
-rw-r--r--src/com/cyanogenmod/filemanager/model/DiskUsage.java44
-rw-r--r--src/com/cyanogenmod/filemanager/model/DiskUsageCategory.java72
-rw-r--r--src/com/cyanogenmod/filemanager/providers/MimeTypeIndexProvider.java221
-rw-r--r--src/com/cyanogenmod/filemanager/service/MimeTypeIndexService.java165
-rw-r--r--src/com/cyanogenmod/filemanager/tasks/FetchStatsByTypeTask.java78
-rw-r--r--src/com/cyanogenmod/filemanager/ui/dialogs/FilesystemInfoDialog.java117
-rw-r--r--src/com/cyanogenmod/filemanager/ui/widgets/DiskUsageGraph.java343
13 files changed, 1020 insertions, 126 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index d442b2a7..88df3cd0 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -62,6 +62,18 @@
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
index 00000000..5b4c5c43
--- /dev/null
+++ b/res/layout/disk_usage_category_view.xml
@@ -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>
diff --git a/res/layout/filesystem_info_dialog.xml b/res/layout/filesystem_info_dialog.xml
index 4b51232b..07303394 100644
--- a/res/layout/filesystem_info_dialog.xml
+++ b/res/layout/filesystem_info_dialog.xml
@@ -351,4 +351,19 @@
</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>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 09d84d50..123860c5 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -65,4 +65,30 @@
<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>
diff --git a/res/values/dimen.xml b/res/values/dimen.xml
index 5bd2fc14..58307cb7 100644
--- a/res/values/dimen.xml
+++ b/res/values/dimen.xml
@@ -124,4 +124,10 @@
<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>
diff --git a/src/com/cyanogenmod/filemanager/FileManagerApplication.java b/src/com/cyanogenmod/filemanager/FileManagerApplication.java
index ebf369a1..0529b97b 100644
--- a/src/com/cyanogenmod/filemanager/FileManagerApplication.java
+++ b/src/com/cyanogenmod/filemanager/FileManagerApplication.java
@@ -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");
+
}
/**
diff --git a/src/com/cyanogenmod/filemanager/model/DiskUsage.java b/src/com/cyanogenmod/filemanager/model/DiskUsage.java
index 693e39b0..5ce49bf4 100644
--- a/src/com/cyanogenmod/filemanager/model/DiskUsage.java
+++ b/src/com/cyanogenmod/filemanager/model/DiskUsage.java
@@ -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
index 00000000..e1be6a03
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/model/DiskUsageCategory.java
@@ -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
index 00000000..6945bda3
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/providers/MimeTypeIndexProvider.java
@@ -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
index 00000000..7f926920
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/service/MimeTypeIndexService.java
@@ -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
index 00000000..de9cfc6c
--- /dev/null
+++ b/src/com/cyanogenmod/filemanager/tasks/FetchStatsByTypeTask.java
@@ -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);
+ }
+
+}
diff --git a/src/com/cyanogenmod/filemanager/ui/dialogs/FilesystemInfoDialog.java b/src/com/cyanogenmod/filemanager/ui/dialogs/FilesystemInfoDialog.java
index 392f36fd..5ead3192 100644
--- a/src/com/cyanogenmod/filemanager/ui/dialogs/FilesystemInfoDialog.java
+++ b/src/com/cyanogenmod/filemanager/ui/dialogs/FilesystemInfoDialog.java
@@ -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;
}
}
diff --git a/src/com/cyanogenmod/filemanager/ui/widgets/DiskUsageGraph.java b/src/com/cyanogenmod/filemanager/ui/widgets/DiskUsageGraph.java
index ecd0cfa0..bdae373c 100644
--- a/src/com/cyanogenmod/filemanager/ui/widgets/DiskUsageGraph.java
+++ b/src/com/cyanogenmod/filemanager/ui/widgets/DiskUsageGraph.java
@@ -17,20 +17,22 @@
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);