Merge lp:~karni/ubuntuone-android-music/v2-cache-settings into lp:ubuntuone-android-music/v2

Proposed by Michał Karnicki
Status: Merged
Merged at revision: 62
Proposed branch: lp:~karni/ubuntuone-android-music/v2-cache-settings
Merge into: lp:ubuntuone-android-music/v2
Diff against target: 1073 lines (+857/-44)
16 files modified
main/AndroidManifest.xml (+7/-8)
main/res/layout/activity_preferences.xml (+13/-0)
main/res/layout/seek_bar_preference.xml (+54/-0)
main/res/xml/preference_headers.xml (+31/-0)
main/res/xml/preferences_account.xml (+6/-0)
main/res/xml/preferences_cache.xml (+26/-0)
main/src/com/ubuntuone/android/music/Constants.java (+10/-0)
main/src/com/ubuntuone/android/music/UbuntuOneMusic.java (+3/-0)
main/src/com/ubuntuone/android/music/provider/MusicProviderUtils.java (+19/-1)
main/src/com/ubuntuone/android/music/service/MusicServiceSupport.java (+5/-0)
main/src/com/ubuntuone/android/music/ui/HomeActivity.java (+1/-1)
main/src/com/ubuntuone/android/music/ui/PreferencesActivity.java (+269/-0)
main/src/com/ubuntuone/android/music/ui/SettingsActivity.java (+0/-29)
main/src/com/ubuntuone/android/music/util/AccountUtils.java (+2/-5)
main/src/com/ubuntuone/android/music/util/SeekBarPreference.java (+195/-0)
main/src/com/ubuntuone/android/music/util/StorageUtils.java (+216/-0)
To merge this branch: bzr merge lp:~karni/ubuntuone-android-music/v2-cache-settings
Reviewer Review Type Date Requested Status
Diego Sarmentero (community) Approve
Review via email: mp+144973@code.launchpad.net

Commit message

Added cache preferences.

Description of the change

Added storage utility class providing storage related info.
Added custom seek bar preference.
Added tablet ready preference activity.
Implemented cache: storage, size and cleaning preferences.
Misc tiny changes I allowed myself to add in small, separate commits.

To post a comment you must log in.
Revision history for this message
Diego Sarmentero (diegosarmentero) wrote :

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'main/AndroidManifest.xml'
2--- main/AndroidManifest.xml 2013-01-23 02:37:15 +0000
3+++ main/AndroidManifest.xml 2013-01-25 16:20:29 +0000
4@@ -1,19 +1,17 @@
5 <?xml version="1.0" encoding="utf-8"?>
6 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
7 package="com.ubuntuone.android.music"
8+ android:sharedUserId="com.ubuntu.one"
9+ android:sharedUserLabel="@string/shared_user"
10 android:versionCode="1"
11 android:versionName="0.1.0" >
12
13- <!--
14- FIXME Set shared user id:
15- android:sharedUserId="com.ubuntuone.android"
16- android:sharedUserLabel="@string/shared_user"
17- -->
18 <uses-sdk
19 android:minSdkVersion="10"
20 android:targetSdkVersion="14" />
21
22 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
23+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
24 <uses-permission android:name="android.permission.GET_ACCOUNTS" />
25 <uses-permission android:name="android.permission.INTERNET" />
26 <uses-permission android:name="android.permission.READ_PHONE_STATE" />
27@@ -23,6 +21,7 @@
28
29 <application
30 android:name=".UbuntuOneMusic"
31+ android:allowBackup="false"
32 android:label="@string/app_name"
33 android:theme="@style/Theme.Sherlock.Light"
34 android:uiOptions="splitActionBarWhenNarrow" >
35@@ -75,8 +74,8 @@
36 android:screenOrientation="portrait" >
37 </activity>
38 <activity
39- android:name=".ui.SettingsActivity"
40- android:label="U1 Music Settings"
41+ android:name=".ui.PreferencesActivity"
42+ android:label="Ubuntu One Music Settings"
43 android:theme="@style/Theme.Sherlock.Light" >
44 </activity>
45
46@@ -95,8 +94,8 @@
47
48 <provider
49 android:name=".provider.MusicProvider"
50+ android:authorities="com.ubuntuone.android.music"
51 android:exported="false"
52- android:authorities="com.ubuntuone.android.music"
53 android:label="@string/app_name"
54 android:syncable="true"
55 android:writePermission="com.ubuntuone.android.music.permission.WRITE_MUSIC_DATA" />
56
57=== added file 'main/res/layout/activity_preferences.xml'
58--- main/res/layout/activity_preferences.xml 1970-01-01 00:00:00 +0000
59+++ main/res/layout/activity_preferences.xml 2013-01-25 16:20:29 +0000
60@@ -0,0 +1,13 @@
61+<?xml version="1.0" encoding="utf-8"?>
62+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
63+ android:layout_width="match_parent"
64+ android:layout_height="match_parent"
65+ android:orientation="horizontal" >
66+
67+ <fragment
68+ android:id="@+id/settings_fragment"
69+ android:layout_width="match_parent"
70+ android:layout_height="match_parent"
71+ class="com.ubuntuone.android.music.ui.SettingsFragment" />
72+
73+</LinearLayout>
74\ No newline at end of file
75
76=== added file 'main/res/layout/seek_bar_preference.xml'
77--- main/res/layout/seek_bar_preference.xml 1970-01-01 00:00:00 +0000
78+++ main/res/layout/seek_bar_preference.xml 2013-01-25 16:20:29 +0000
79@@ -0,0 +1,54 @@
80+<?xml version="1.0" encoding="utf-8"?>
81+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
82+ android:id="@android:id/widget_frame"
83+ android:layout_width="fill_parent"
84+ android:layout_height="wrap_content"
85+ android:layout_marginBottom="6dp"
86+ android:layout_marginLeft="15dp"
87+ android:layout_marginRight="6dp"
88+ android:layout_marginTop="6dp"
89+ android:gravity="center_vertical"
90+ android:minHeight="?android:attr/listPreferredItemHeight"
91+ android:paddingLeft="8dp"
92+ android:paddingRight="?android:attr/scrollbarSize" >
93+
94+ <TextView
95+ android:id="@android:id/title"
96+ android:layout_width="fill_parent"
97+ android:layout_height="wrap_content"
98+ android:layout_alignParentLeft="true"
99+ android:layout_alignParentTop="true"
100+ android:ellipsize="marquee"
101+ android:fadingEdge="horizontal"
102+ android:singleLine="true"
103+ android:textAppearance="?android:attr/textAppearanceMedium" />
104+
105+ <TextView
106+ android:id="@android:id/summary"
107+ android:layout_width="fill_parent"
108+ android:layout_height="wrap_content"
109+ android:layout_alignParentLeft="true"
110+ android:layout_below="@android:id/title"
111+ android:textAppearance="?android:attr/textAppearanceSmall"
112+ android:textColor="?android:attr/textColorSecondary" />
113+
114+ <TextView
115+ android:id="@+id/seekBarPrefValue"
116+ android:layout_width="wrap_content"
117+ android:layout_height="wrap_content"
118+ android:layout_below="@android:id/title"
119+ android:layout_alignParentRight="true"
120+ android:gravity="right"
121+ android:textAppearance="?android:attr/textAppearanceSmall"
122+ android:textColor="?android:attr/textColorSecondary" />
123+
124+ <LinearLayout
125+ android:id="@+id/seekBarPrefBarContainer"
126+ android:layout_width="fill_parent"
127+ android:layout_height="wrap_content"
128+ android:layout_alignParentBottom="true"
129+ android:layout_alignParentLeft="true"
130+ android:layout_below="@android:id/summary" >
131+ </LinearLayout>
132+
133+</RelativeLayout>
134\ No newline at end of file
135
136=== added directory 'main/res/xml'
137=== added file 'main/res/xml/preference_headers.xml'
138--- main/res/xml/preference_headers.xml 1970-01-01 00:00:00 +0000
139+++ main/res/xml/preference_headers.xml 2013-01-25 16:20:29 +0000
140@@ -0,0 +1,31 @@
141+<?xml version="1.0" encoding="utf-8"?>
142+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android" >
143+
144+ <header
145+ android:fragment="com.ubuntuone.android.music.ui.PreferencesActivity$PreferencesFragment"
146+ android:icon="@android:drawable/ic_menu_help"
147+ android:summary="(placeholder item)"
148+ android:title="Account" >
149+ <extra
150+ android:name="preferenceResource"
151+ android:value="preferences_account" />
152+ </header>
153+ <header
154+ android:fragment="com.ubuntuone.android.music.ui.PreferencesActivity$PreferencesFragment"
155+ android:icon="@android:drawable/ic_menu_manage"
156+ android:summary="Manage used space"
157+ android:title="Cache" >
158+ <extra
159+ android:name="preferenceResource"
160+ android:value="preferences_cache" />
161+ </header>
162+ <header
163+ android:icon="@android:drawable/ic_menu_agenda"
164+ android:summary="(placeholder item)"
165+ android:title="Ubuntu One Blog" >
166+ <intent
167+ android:action="android.intent.action.VIEW"
168+ android:data="https://one.ubuntu.com/blog" />
169+ </header>
170+
171+</preference-headers>
172\ No newline at end of file
173
174=== added file 'main/res/xml/preferences_account.xml'
175--- main/res/xml/preferences_account.xml 1970-01-01 00:00:00 +0000
176+++ main/res/xml/preferences_account.xml 2013-01-25 16:20:29 +0000
177@@ -0,0 +1,6 @@
178+<?xml version="1.0" encoding="utf-8"?>
179+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
180+ xmlns:ubuntuone="http://schemas.one.ubuntu.com/apk/res/android"
181+ android:key="preference_screen" >
182+
183+</PreferenceScreen>
184\ No newline at end of file
185
186=== added file 'main/res/xml/preferences_cache.xml'
187--- main/res/xml/preferences_cache.xml 1970-01-01 00:00:00 +0000
188+++ main/res/xml/preferences_cache.xml 2013-01-25 16:20:29 +0000
189@@ -0,0 +1,26 @@
190+<?xml version="1.0" encoding="utf-8"?>
191+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
192+ xmlns:ubuntuone="http://schemas.one.ubuntu.com/apk/res/android"
193+ android:key="preference_screen" >
194+
195+ <PreferenceCategory
196+ android:key="preferenceCategory"
197+ android:title="Cache" >
198+ <ListPreference
199+ android:key="cacheLocation"
200+ android:persistent="true"
201+ android:title="Storage" />
202+
203+ <com.ubuntuone.android.music.util.SeekBarPreference
204+ android:key="cacheSize"
205+ android:persistent="true"
206+ android:summary="Adjust allowed space"
207+ android:title="Size" />
208+
209+ <Preference
210+ android:key="clearCache"
211+ android:summary="Deletes non-favorite songs"
212+ android:title="Clear non-favorites" />
213+ </PreferenceCategory>
214+
215+</PreferenceScreen>
216\ No newline at end of file
217
218=== added file 'main/src/com/ubuntuone/android/music/Constants.java'
219--- main/src/com/ubuntuone/android/music/Constants.java 1970-01-01 00:00:00 +0000
220+++ main/src/com/ubuntuone/android/music/Constants.java 2013-01-25 16:20:29 +0000
221@@ -0,0 +1,10 @@
222+package com.ubuntuone.android.music;
223+
224+public class Constants {
225+ public static interface Preference {
226+ String CACHE_LOCATION_KEY = "cacheLocation";
227+ String CACHE_DIR_KEY = "cacheDir";
228+ String CACHE_SIZE_KEY = "cacheSize";
229+ String CLEAR_CACHE_KEY = "clearCache";
230+ }
231+}
232
233=== modified file 'main/src/com/ubuntuone/android/music/UbuntuOneMusic.java'
234--- main/src/com/ubuntuone/android/music/UbuntuOneMusic.java 2013-01-23 02:36:20 +0000
235+++ main/src/com/ubuntuone/android/music/UbuntuOneMusic.java 2013-01-25 16:20:29 +0000
236@@ -37,6 +37,7 @@
237 import com.ubuntuone.android.music.util.HttpManager;
238 import com.ubuntuone.android.music.util.Log;
239 import com.ubuntuone.android.music.util.NotAuthenticatedException;
240+import com.ubuntuone.android.music.util.StorageUtils;
241 import com.ubuntuone.api.music.U1MusicAPI;
242 import com.ubuntuone.api.sso.authorizer.OAuthAuthorizer;
243
244@@ -67,6 +68,8 @@
245 super.onCreate();
246 instance = this;
247 setupLogging();
248+
249+ StorageUtils.setupMusicDirectory(this);
250 }
251
252 private void setupLogging() {
253
254=== modified file 'main/src/com/ubuntuone/android/music/provider/MusicProviderUtils.java'
255--- main/src/com/ubuntuone/android/music/provider/MusicProviderUtils.java 2012-12-14 04:29:46 +0000
256+++ main/src/com/ubuntuone/android/music/provider/MusicProviderUtils.java 2013-01-25 16:20:29 +0000
257@@ -206,7 +206,25 @@
258 String selection = Songs.SONG_PATH + " IS NOT NULL";
259 return getSongCount(context, selection);
260 }
261-
262+
263+ public static long getCachedSongSize(Context context) {
264+ ContentResolver resolver = context.getContentResolver();
265+ String[] projection = new String[] {
266+ "SUM(" + Songs.SONG_SIZE + ") as cache_size_used"
267+ };
268+ Cursor cursor = null;
269+ long size = 0;
270+ try {
271+ cursor = resolver.query(Songs.CACHED_SONGS_URI, projection, null, null, null);
272+ if (cursor != null && cursor.moveToFirst()) {
273+ size = cursor.getLong(cursor.getColumnIndex("cache_size_used"));
274+ }
275+ } finally {
276+ if (cursor != null) cursor.close();
277+ }
278+ return size;
279+ }
280+
281 public static int getPlaylistSongCount(Context context, Uri playlistUri) {
282 ContentResolver resolver = context.getContentResolver();
283 String playlistId = Playlists.getPlaylistId(playlistUri);
284
285=== modified file 'main/src/com/ubuntuone/android/music/service/MusicServiceSupport.java'
286--- main/src/com/ubuntuone/android/music/service/MusicServiceSupport.java 2012-12-14 04:29:46 +0000
287+++ main/src/com/ubuntuone/android/music/service/MusicServiceSupport.java 2013-01-25 16:20:29 +0000
288@@ -56,6 +56,7 @@
289 private MusicService mMusicService;
290 private BroadcastReceiver mHeadsetBroadcastReceiver;
291 private PhoneStateListener mPhoneStateListener;
292+ private boolean mWasOnCreateCalled = false;
293
294 public MusicServiceSupport(MusicService service) {
295 this.mMusicService = service;
296@@ -64,6 +65,7 @@
297 }
298
299 public void onCreate() {
300+ mWasOnCreateCalled = true;
301 registerHeadsetBroadcastReceiver();
302 registerMediaButtonBroadcastReceiver();
303 registerPhoneStateListener();
304@@ -79,6 +81,9 @@
305 }
306
307 public void onDestroy() {
308+ if (!mWasOnCreateCalled) {
309+ return;
310+ }
311 unregisterHeadsetBroadcastReceiver();
312 unregisterMediaButtonBroadcastReceiver();
313 unregisterPhoneStateListener();
314
315=== modified file 'main/src/com/ubuntuone/android/music/ui/HomeActivity.java'
316--- main/src/com/ubuntuone/android/music/ui/HomeActivity.java 2013-01-23 02:36:20 +0000
317+++ main/src/com/ubuntuone/android/music/ui/HomeActivity.java 2013-01-25 16:20:29 +0000
318@@ -419,7 +419,7 @@
319 }
320
321 private boolean onSettingsOptionItemSelected() {
322- Intent settingsIntent = new Intent(this, SettingsActivity.class);
323+ Intent settingsIntent = new Intent(this, PreferencesActivity.class);
324 startActivity(settingsIntent);
325 return true;
326 }
327
328=== added file 'main/src/com/ubuntuone/android/music/ui/PreferencesActivity.java'
329--- main/src/com/ubuntuone/android/music/ui/PreferencesActivity.java 1970-01-01 00:00:00 +0000
330+++ main/src/com/ubuntuone/android/music/ui/PreferencesActivity.java 2013-01-25 16:20:29 +0000
331@@ -0,0 +1,269 @@
332+/*
333+ * Ubuntu One Music - stream music from Ubuntu One cloud storage.
334+ *
335+ * Copyright 2012-2013 Canonical Ltd.
336+ *
337+ * This file is part of Ubuntu One Music app.
338+ *
339+ * This program is free software: you can redistribute it and/or modify
340+ * it under the terms of the GNU Affero General Public License as
341+ * published by the Free Software Foundation, either version 3 of the
342+ * License, or (at your option) any later version.
343+ *
344+ * This program is distributed in the hope that it will be useful,
345+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
346+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
347+ * GNU Affero General Public License for more details.
348+ *
349+ * You should have received a copy of the GNU Affero General Public License
350+ * along with this program. If not, see http://www.gnu.org/licenses
351+ */
352+
353+package com.ubuntuone.android.music.ui;
354+
355+import static com.ubuntuone.android.music.util.LogUtils.makeLogTag;
356+import static com.ubuntuone.android.music.util.StorageUtils.MiB;
357+
358+import java.io.File;
359+import java.lang.reflect.InvocationTargetException;
360+import java.lang.reflect.Method;
361+import java.util.Arrays;
362+import java.util.List;
363+
364+import android.annotation.TargetApi;
365+import android.content.ContentResolver;
366+import android.content.ContentValues;
367+import android.content.Context;
368+import android.database.Cursor;
369+import android.net.Uri;
370+import android.os.AsyncTask;
371+import android.os.Build;
372+import android.os.Bundle;
373+import android.preference.ListPreference;
374+import android.preference.Preference;
375+import android.preference.Preference.OnPreferenceChangeListener;
376+import android.preference.Preference.OnPreferenceClickListener;
377+import android.preference.PreferenceActivity;
378+import android.preference.PreferenceFragment;
379+import android.preference.PreferenceManager;
380+import android.util.Log;
381+import android.widget.Toast;
382+
383+import com.ubuntuone.android.music.Constants;
384+import com.ubuntuone.android.music.R;
385+import com.ubuntuone.android.music.provider.MusicContract.Songs;
386+import com.ubuntuone.android.music.provider.MusicProviderUtils;
387+import com.ubuntuone.android.music.util.SeekBarPreference;
388+import com.ubuntuone.android.music.util.StorageUtils;
389+import com.ubuntuone.android.music.util.StorageUtils.InvalidStorage;
390+import com.ubuntuone.android.music.util.UIUtils;
391+
392+public class PreferencesActivity extends PreferenceActivity implements OnPreferenceClickListener {
393+ private static final String TAG = makeLogTag(PreferencesActivity.class.getSimpleName());
394+
395+ protected Method mLoadHeaders = null;
396+ protected Method mHasHeaders = null;
397+ protected boolean mIsTablet;
398+
399+ protected ClearCacheTask mClearCacheTask;
400+
401+ public boolean hasHeadersCompat() {
402+ if (mHasHeaders != null && mLoadHeaders != null) {
403+ try {
404+ return (Boolean) mHasHeaders.invoke(this);
405+ } catch (IllegalArgumentException e) {
406+ } catch (IllegalAccessException e) {
407+ } catch (InvocationTargetException e) {
408+ }
409+ }
410+ return false;
411+ }
412+
413+ @SuppressWarnings("deprecation")
414+ @Override
415+ public void onCreate(Bundle aSavedState) {
416+ // onBuildHeaders() will be called during super.onCreate()
417+ try {
418+ mLoadHeaders = getClass().getMethod("loadHeadersFromResource", int.class, List.class);
419+ mHasHeaders = getClass().getMethod("hasHeaders");
420+ } catch (NoSuchMethodException e) {
421+ // We're handling an old API.
422+ }
423+ mIsTablet = UIUtils.isTablet(this);
424+ super.onCreate(aSavedState);
425+ if (!hasHeadersCompat() || !mIsTablet) {
426+ addPreferencesFromResource(R.xml.preferences_account);
427+ addPreferencesFromResource(R.xml.preferences_cache);
428+ }
429+
430+ setupCacheLocationPreference();
431+ setupCacheSizePreference(this);
432+ setupClearCachePreference(this);
433+ }
434+
435+ @Override
436+ public void onBuildHeaders(List<Header> target) {
437+ try {
438+ if (mIsTablet) {
439+ mLoadHeaders.invoke(this, new Object[] { R.xml.preference_headers, target });
440+ }
441+ } catch (IllegalArgumentException e) {
442+ } catch (IllegalAccessException e) {
443+ } catch (InvocationTargetException e) {
444+ }
445+ }
446+
447+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
448+ static public class PreferencesFragment extends PreferenceFragment {
449+ @Override
450+ public void onCreate(Bundle savedInstanceState) {
451+ super.onCreate(savedInstanceState);
452+ Context context = getActivity().getApplicationContext();
453+ int preferencesResource = context.getResources().getIdentifier(
454+ getArguments().getString("preferenceResource"), "xml",
455+ context.getPackageName());
456+ PreferenceManager.setDefaultValues(context, preferencesResource, false);
457+ addPreferencesFromResource(preferencesResource);
458+ // Hack. On tablets, hide the category title duplicated in fragment title.
459+ findPreference("preferenceCategory").setTitle("");
460+ }
461+ }
462+
463+ public void setupCacheLocationPreference() {
464+ @SuppressWarnings("deprecation")
465+ final ListPreference cacheLocation = (ListPreference) findPreference(
466+ Constants.Preference.CACHE_LOCATION_KEY);
467+ final String[] storagePaths = StorageUtils.getAvailableStoragePaths();
468+ Log.i(TAG, "Available storage paths: " + Arrays.toString(storagePaths));
469+ if (storagePaths != null) {
470+ cacheLocation.setEntries(storagePaths);
471+ cacheLocation.setEntryValues(storagePaths);
472+
473+ String storage = cacheLocation.getValue();
474+ cacheLocation.setValue(storage);
475+ cacheLocation.setSummary(storage);
476+ try {
477+ int index = StorageUtils.getSelectedStorageIndex(storage, storagePaths);
478+ cacheLocation.setValueIndex(index);
479+ } catch (InvalidStorage e) {
480+ // Should not happen, as the value has been already set.
481+ }
482+
483+ cacheLocation.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
484+ @Override
485+ public boolean onPreferenceChange(Preference preference, Object newValue) {
486+ // TODO karni: Move cached music to new location or at least notify the user.
487+ String location = (String) newValue;
488+ cacheLocation.setSummary(location);
489+ return true;
490+ }
491+ });
492+ } else {
493+ cacheLocation.setEnabled(false);
494+ }
495+ }
496+
497+ public void setupCacheSizePreference(Context context) {
498+ @SuppressWarnings("deprecation")
499+ SeekBarPreference seekBarPreference = (SeekBarPreference) findPreference(
500+ Constants.Preference.CACHE_SIZE_KEY);
501+ String storage = StorageUtils.getMusicDirectory(context);
502+
503+ long cacheSize = MusicProviderUtils.getCachedSongSize(context);
504+ long min = Math.max(StorageUtils.MIN_CACHE_SIZE, cacheSize);
505+ long max = StorageUtils.getAvailableStorage(context, storage) - cacheSize;
506+
507+ seekBarPreference.setMinValue((int)(min / MiB));
508+ seekBarPreference.setMaxValue((int)(max / MiB));
509+ seekBarPreference.setDefaultValue((int)(max / MiB));
510+ }
511+
512+ public void setupClearCachePreference(Context context) {
513+ @SuppressWarnings("deprecation")
514+ Preference clearCachePreference = findPreference(Constants.Preference.CLEAR_CACHE_KEY);
515+ long cacheSizeUsed = MusicProviderUtils.getCachedSongSize(context);
516+ String fuzzySpaceUsed = StorageUtils.getFuzzySize(cacheSizeUsed);
517+ Log.i(TAG, String.format("Cache stats: songs %d used %s",
518+ MusicProviderUtils.getCachedSongCount(context), fuzzySpaceUsed));
519+ clearCachePreference.setSummary(String.format("Space used: %s", fuzzySpaceUsed));
520+ clearCachePreference.setOnPreferenceClickListener(this);
521+ }
522+
523+ @Override
524+ public boolean onPreferenceClick(Preference preference) {
525+ String key = preference.getKey();
526+ if (Constants.Preference.CLEAR_CACHE_KEY.equals(key)) {
527+ if (mClearCacheTask == null) {
528+ mClearCacheTask = new ClearCacheTask();
529+ mClearCacheTask.execute();
530+ }
531+ }
532+ return false;
533+ }
534+
535+ public class ClearCacheTask extends AsyncTask<Integer, Void, Integer> {
536+ private Context mContext = getApplicationContext();
537+
538+ @Override
539+ protected void onPreExecute() {
540+ Toast.makeText(mContext, "Clearing cache...", Toast.LENGTH_SHORT).show();
541+ }
542+
543+ @Override
544+ protected Integer doInBackground(Integer... params) {
545+ ContentResolver resolver = mContext.getContentResolver();
546+ String[] projection = new String[] { Songs.SONG_ID, Songs.SONG_PATH };
547+ String selection = Songs.STARRED + "!=1";
548+
549+ Cursor cursor = null;
550+ Integer deletedSongs = 0;
551+ try {
552+ cursor = resolver.query(Songs.CACHED_SONGS_URI, projection, selection, null, null);
553+ if (cursor != null && cursor.moveToFirst()) {
554+ int pathColumnIndex = cursor.getColumnIndex(Songs.SONG_PATH);
555+ int songIdColumnIndex = cursor.getColumnIndex(Songs.SONG_ID);
556+ do {
557+ if (deleteSong(resolver, cursor, pathColumnIndex, songIdColumnIndex)) {
558+ deletedSongs++;
559+ }
560+ } while (cursor.moveToNext());
561+ }
562+ } finally {
563+ if (cursor != null) cursor.close();
564+ }
565+ return deletedSongs;
566+ }
567+
568+ private boolean deleteSong(ContentResolver resolver, Cursor cursor,
569+ int pathColumnIndex, int songIdColumnIndex) {
570+ String path = cursor.getString(pathColumnIndex);
571+ String songId = cursor.getString(songIdColumnIndex);
572+ File songFile = new File(path);
573+ if (songFile.exists()) {
574+ Uri songUri = Songs.buildSongUri(songId);
575+ ContentValues values = new ContentValues();
576+ values.putNull(Songs.SONG_PATH);
577+ boolean entryUpdated = resolver.update(songUri, values, null, null) == 1;
578+ return songFile.delete() && entryUpdated;
579+ }
580+ return false;
581+ }
582+
583+ @Override
584+ protected void onPostExecute(Integer result) {
585+ Toast.makeText(mContext, String.format("Deleted %d songs.", result),
586+ Toast.LENGTH_SHORT).show();
587+ mClearCacheTask = null;
588+
589+ if (!isFinishing()) {
590+ setupClearCachePreference(PreferencesActivity.this);
591+ }
592+ }
593+
594+ @Override
595+ protected void onCancelled() {
596+ Toast.makeText(mContext, "Canceled.", Toast.LENGTH_SHORT).show();
597+ mClearCacheTask = null;
598+ }
599+ }
600+}
601
602=== removed file 'main/src/com/ubuntuone/android/music/ui/SettingsActivity.java'
603--- main/src/com/ubuntuone/android/music/ui/SettingsActivity.java 2012-12-14 04:29:46 +0000
604+++ main/src/com/ubuntuone/android/music/ui/SettingsActivity.java 1970-01-01 00:00:00 +0000
605@@ -1,29 +0,0 @@
606-/*
607- * Ubuntu One Music - stream music from Ubuntu One cloud storage.
608- *
609- * Copyright 2012 Canonical Ltd.
610- *
611- * This file is part of Ubuntu One Music app.
612- *
613- * This program is free software: you can redistribute it and/or modify
614- * it under the terms of the GNU Affero General Public License as
615- * published by the Free Software Foundation, either version 3 of the
616- * License, or (at your option) any later version.
617- *
618- * This program is distributed in the hope that it will be useful,
619- * but WITHOUT ANY WARRANTY; without even the implied warranty of
620- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
621- * GNU Affero General Public License for more details.
622- *
623- * You should have received a copy of the GNU Affero General Public License
624- * along with this program. If not, see http://www.gnu.org/licenses
625- */
626-
627-package com.ubuntuone.android.music.ui;
628-
629-import com.actionbarsherlock.app.SherlockPreferenceActivity;
630-
631-public class SettingsActivity extends SherlockPreferenceActivity
632-{
633-
634-}
635
636=== modified file 'main/src/com/ubuntuone/android/music/util/AccountUtils.java'
637--- main/src/com/ubuntuone/android/music/util/AccountUtils.java 2013-01-23 02:36:20 +0000
638+++ main/src/com/ubuntuone/android/music/util/AccountUtils.java 2013-01-25 16:20:29 +0000
639@@ -92,11 +92,8 @@
640 if (accounts.length > 0) {
641 return U1_ACCOUNT_TYPE;
642 }
643- accounts = accountManager.getAccountsByType(OLD_U1_ACCOUNT_TYPE);
644- if (accounts.length > 0) {
645- return U1_ACCOUNT_TYPE;
646- }
647- return null;
648+ // Otherwise, always return the old account type, regardless if the account exists.
649+ return OLD_U1_ACCOUNT_TYPE;
650 }
651
652 public static String getChosenAccountName(final Context context) {
653
654=== added file 'main/src/com/ubuntuone/android/music/util/SeekBarPreference.java'
655--- main/src/com/ubuntuone/android/music/util/SeekBarPreference.java 1970-01-01 00:00:00 +0000
656+++ main/src/com/ubuntuone/android/music/util/SeekBarPreference.java 2013-01-25 16:20:29 +0000
657@@ -0,0 +1,195 @@
658+package com.ubuntuone.android.music.util;
659+
660+import static com.ubuntuone.android.music.util.StorageUtils.MiB;
661+import android.content.Context;
662+import android.content.res.TypedArray;
663+import android.preference.Preference;
664+import android.util.AttributeSet;
665+import android.util.Log;
666+import android.view.LayoutInflater;
667+import android.view.View;
668+import android.view.ViewGroup;
669+import android.view.ViewParent;
670+import android.widget.RelativeLayout;
671+import android.widget.SeekBar;
672+import android.widget.SeekBar.OnSeekBarChangeListener;
673+import android.widget.TextView;
674+
675+import com.ubuntuone.android.music.R;
676+
677+public class SeekBarPreference extends Preference implements
678+ OnSeekBarChangeListener {
679+ private final String TAG = getClass().getName();
680+
681+ private static final String ANDROID_NS = "http://schemas.android.com/apk/res/android";
682+ private static final String UBUNTUONE_NS = "http://schemas.one.ubuntu.com/apk/res/android";
683+ private static final int DEFAULT_VALUE = 50;
684+
685+ private int mMaxValue = 100;
686+ private int mMinValue = 0;
687+ private int mInterval = 1;
688+ private int mCurrentValue;
689+ private SeekBar mSeekBar;
690+
691+ private TextView mStatusText;
692+
693+ public void setMinValue(int min) {
694+ mMinValue = min;
695+ mSeekBar.setMax(mMaxValue - mMinValue);
696+ }
697+
698+ public void setMaxValue(int max) {
699+ mMaxValue = max;
700+ mSeekBar.setMax(mMaxValue - mMinValue);
701+ }
702+
703+ public SeekBarPreference(Context context, AttributeSet attrs) {
704+ super(context, attrs);
705+ initPreference(context, attrs);
706+ }
707+
708+ public SeekBarPreference(Context context, AttributeSet attrs, int defStyle) {
709+ super(context, attrs, defStyle);
710+ initPreference(context, attrs);
711+ }
712+
713+ private void initPreference(Context context, AttributeSet attrs) {
714+ setValuesFromXml(attrs);
715+ mSeekBar = new SeekBar(context, attrs);
716+ mSeekBar.setMax(mMaxValue - mMinValue);
717+ mSeekBar.setOnSeekBarChangeListener(this);
718+ }
719+
720+ private void setValuesFromXml(AttributeSet attrs) {
721+ mMaxValue = attrs.getAttributeIntValue(ANDROID_NS, "max", 100);
722+ mMinValue = attrs.getAttributeIntValue(UBUNTUONE_NS, "min", 0);
723+
724+ try {
725+ String newInterval = attrs.getAttributeValue(UBUNTUONE_NS, "interval");
726+ if (newInterval != null) {
727+ mInterval = Integer.parseInt(newInterval);
728+ }
729+ } catch (Exception e) {
730+ Log.e(TAG, "Invalid interval value", e);
731+ }
732+
733+ }
734+
735+ @SuppressWarnings("unused")
736+ private String getAttributeStringValue(AttributeSet attrs,
737+ String namespace, String name, String defaultValue) {
738+ String value = attrs.getAttributeValue(namespace, name);
739+ if (value == null) {
740+ value = defaultValue;
741+ }
742+ return value;
743+ }
744+
745+ @Override
746+ protected View onCreateView(ViewGroup parent) {
747+ RelativeLayout layout = null;
748+ try {
749+ LayoutInflater mInflater = (LayoutInflater) getContext()
750+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
751+
752+ layout = (RelativeLayout) mInflater.inflate(
753+ R.layout.seek_bar_preference, parent, false);
754+ } catch (Exception e) {
755+ Log.e(TAG, "Error creating seek bar preference", e);
756+ }
757+ return layout;
758+ }
759+
760+ @Override
761+ public void onBindView(View view) {
762+ super.onBindView(view);
763+ try {
764+ ViewParent oldContainer = mSeekBar.getParent();
765+ ViewGroup newContainer = (ViewGroup) view
766+ .findViewById(R.id.seekBarPrefBarContainer);
767+
768+ if (oldContainer != newContainer) {
769+ if (oldContainer != null) {
770+ ((ViewGroup) oldContainer).removeView(mSeekBar);
771+ }
772+ newContainer.removeAllViews();
773+ newContainer.addView(mSeekBar,
774+ ViewGroup.LayoutParams.MATCH_PARENT,
775+ ViewGroup.LayoutParams.WRAP_CONTENT);
776+ }
777+ } catch (Exception ex) {
778+ Log.e(TAG, "Error binding view: " + ex.toString());
779+ }
780+ updateView(view);
781+ }
782+
783+ /**
784+ * Update a SeekBarPreference view with our current state
785+ *
786+ * @param view
787+ */
788+ protected void updateView(View view) {
789+
790+ try {
791+ RelativeLayout layout = (RelativeLayout) view;
792+ mStatusText = (TextView) layout.findViewById(R.id.seekBarPrefValue);
793+ mStatusText.setMinimumWidth(30);
794+ mSeekBar.setProgress(mCurrentValue - mMinValue);
795+
796+ mStatusText.setText(StorageUtils.getFuzzySize(mCurrentValue * MiB));
797+ } catch (Exception e) {
798+ Log.e(TAG, "Error updating seek bar preference", e);
799+ }
800+
801+ }
802+
803+ @Override
804+ public void onProgressChanged(SeekBar seekBar, int progress,
805+ boolean fromUser) {
806+ int newValue = progress + mMinValue;
807+
808+ if (newValue > mMaxValue)
809+ newValue = mMaxValue;
810+ else if (newValue < mMinValue)
811+ newValue = mMinValue;
812+ else if (mInterval != 1 && newValue % mInterval != 0)
813+ newValue = Math.round(((float) newValue) / mInterval) * mInterval;
814+
815+ mCurrentValue = newValue;
816+ if (mStatusText != null) {
817+ mStatusText.setText(StorageUtils.getFuzzySize(newValue * MiB));
818+ }
819+ persistInt(newValue);
820+ }
821+
822+ @Override
823+ public void onStartTrackingTouch(SeekBar seekBar) {
824+ }
825+
826+ @Override
827+ public void onStopTrackingTouch(SeekBar seekBar) {
828+ notifyChanged();
829+ }
830+
831+ @Override
832+ protected Object onGetDefaultValue(TypedArray typedArray, int index) {
833+ int defaultValue = typedArray.getInt(index, DEFAULT_VALUE);
834+ return defaultValue;
835+ }
836+
837+ @Override
838+ protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
839+ if (restoreValue) {
840+ mCurrentValue = getPersistedInt(mCurrentValue);
841+ } else {
842+ int value = 0;
843+ try {
844+ value = (Integer) defaultValue;
845+ } catch (Exception ex) {
846+ Log.e(TAG, "Invalid default value: " + defaultValue.toString());
847+ }
848+ persistInt(value);
849+ mCurrentValue = value;
850+ }
851+ }
852+}
853
854=== added file 'main/src/com/ubuntuone/android/music/util/StorageUtils.java'
855--- main/src/com/ubuntuone/android/music/util/StorageUtils.java 1970-01-01 00:00:00 +0000
856+++ main/src/com/ubuntuone/android/music/util/StorageUtils.java 2013-01-25 16:20:29 +0000
857@@ -0,0 +1,216 @@
858+/*
859+ * Ubuntu One Music - stream music from Ubuntu One cloud storage.
860+ *
861+ * Copyright 2013 Canonical Ltd.
862+ *
863+ * This file is part of Ubuntu One Music app.
864+ *
865+ * This program is free software: you can redistribute it and/or modify
866+ * it under the terms of the GNU Affero General Public License as
867+ * published by the Free Software Foundation, either version 3 of the
868+ * License, or (at your option) any later version.
869+ *
870+ * This program is distributed in the hope that it will be useful,
871+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
872+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
873+ * GNU Affero General Public License for more details.
874+ *
875+ * You should have received a copy of the GNU Affero General Public License
876+ * along with this program. If not, see http://www.gnu.org/licenses
877+ */
878+
879+package com.ubuntuone.android.music.util;
880+
881+import static com.ubuntuone.android.music.util.LogUtils.makeLogTag;
882+
883+import java.io.File;
884+import java.util.ArrayList;
885+import java.util.Arrays;
886+import java.util.Locale;
887+
888+import android.annotation.SuppressLint;
889+import android.content.Context;
890+import android.content.SharedPreferences;
891+import android.os.Build;
892+import android.os.Environment;
893+import android.os.StatFs;
894+import android.preference.PreferenceManager;
895+import android.util.Log;
896+
897+import com.ubuntuone.android.music.Constants;
898+import com.ubuntuone.android.music.Constants.Preference;
899+
900+/** Utility functions to query and store information about the SDCard "external storage". */
901+public final class StorageUtils {
902+ private static final String TAG = makeLogTag(StorageUtils.class.getSimpleName());
903+
904+ public static final String MUSIC_DIR = "u1music";
905+
906+ public static final long MiB = 1024*1024;
907+ public static final long MIN_CACHE_SIZE = 100 * MiB;
908+
909+ private StorageUtils() {}
910+
911+ public static void setupMusicDirectory(Context context) {
912+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
913+ String storage = preferences.getString(Preference.CACHE_LOCATION_KEY, null);
914+ if (storage == null) {
915+ final String[] storagePaths = StorageUtils.getAvailableStoragePaths();
916+ Log.i(TAG, "Available storage paths: " + Arrays.toString(storagePaths));
917+ if (storagePaths != null) {
918+ storage = storagePaths[0];
919+ } else {
920+ storage = getGenericCacheDirectory();
921+ }
922+ PreferenceManager.getDefaultSharedPreferences(context).edit()
923+ .putString(Constants.Preference.CACHE_LOCATION_KEY, storage).commit();
924+
925+ } else {
926+ Log.w(TAG, "No storage paths matched on " + Build.MODEL);
927+ }
928+
929+ long avail = getAvailableStorage(context, storage);
930+ long total = getTotalStorage(context, storage);
931+ Log.i(TAG, String.format("Storage stats: avail %s total %s",
932+ getFuzzySize(avail), getFuzzySize(total)));
933+ }
934+
935+ /**
936+ * Returns the directory used to store cached music collection located on the selected storage.
937+ * <br />
938+ * Prerequisite: <b>setupMusicDirectory</b> must be called before calling this method,
939+ * to initialize the default value of cache location preference.
940+ *
941+ * @param context
942+ *
943+ * @return Absolute path of the music storage directory.
944+ */
945+ public static String getMusicDirectory(Context context) {
946+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
947+ String cacheStorage = preferences.getString(Preference.CACHE_LOCATION_KEY, null);
948+ String cacheDirectory = preferences.getString(Preference.CACHE_DIR_KEY, MUSIC_DIR);
949+ if (cacheStorage == null) {
950+ throw new IllegalStateException("storage is null, have you called setupMusicDirectory?");
951+ }
952+ File storageDirectory = new File(cacheStorage);
953+ File musicDirectory = new File(storageDirectory, cacheDirectory);
954+ if (!musicDirectory.exists()) {
955+ musicDirectory.mkdirs();
956+ }
957+ return musicDirectory.getAbsolutePath();
958+ }
959+
960+ /**
961+ * @return Absolute path of the default, generic music storage directory.
962+ */
963+ public static String getGenericCacheDirectory() {
964+ final File storageDirectory = Environment.getExternalStorageDirectory();
965+ final File genericCache = new File(storageDirectory,
966+ "Android/data/com.ubuntuone.android.music/files/cache");
967+ if (!genericCache.isDirectory()) {
968+ Log.i(TAG, "Creating cache directory.");
969+ genericCache.delete(); // Doesn't throw IOException
970+ genericCache.mkdirs();
971+ }
972+ return genericCache.getAbsolutePath();
973+ }
974+
975+ public static String getExternalStoragePath() {
976+ return Environment.getExternalStorageDirectory().getAbsolutePath();
977+ }
978+
979+ @SuppressLint("SdCardPath") // Yada yada lint. getExternalStorageDirectory() is NOT enough.
980+ private static final String[] knownStoragePaths = new String[] {
981+ "/storage/sdcard0",
982+ "/mnt/sdcard",
983+ "/mnt/sdcard2",
984+ "/mnt/sdcard-ext", // Motorola
985+ "/mnt/sdcard/external_sd", // Samsung
986+ "/mnt/ext_card", // Samsung
987+ "/sdcard/_ExternalSD"
988+ };
989+
990+ public static String[] getAvailableStoragePaths() {
991+ ArrayList<String> pathList = new ArrayList<String>();
992+ for (String path : knownStoragePaths) {
993+ File storage = new File(path);
994+ if (storage.exists() && storage.isDirectory()) {
995+ pathList.add(storage.getAbsolutePath());
996+ }
997+ }
998+ int size = pathList.size();
999+ return pathList.toArray(new String[size]);
1000+ }
1001+
1002+ public static int getSelectedStorageIndex(String storage,
1003+ String[] storagePaths) throws InvalidStorage {
1004+ for (int i = 0; i < storagePaths.length; i++) {
1005+ if (storage.equals(storagePaths[i]))
1006+ return i;
1007+ }
1008+ throw new InvalidStorage();
1009+ }
1010+
1011+ public static long getAvailableStorage(Context context, String storage) {
1012+ StatFs stat = new StatFs(getMusicDirectory(context));
1013+ long blocksAvailable = stat.getAvailableBlocks();
1014+ long blockSizeBytes = stat.getBlockSize();
1015+ return blocksAvailable * blockSizeBytes;
1016+ }
1017+
1018+ /**
1019+ * total space - free space - used cache size
1020+ *
1021+ * @param context
1022+ * @param storage
1023+ * @return
1024+ */
1025+ public static long getAvailableCacheStorage(Context context, String storage, long cacheSize) {
1026+ StatFs stat = new StatFs(getMusicDirectory(context));
1027+ long blocksAvailable = stat.getAvailableBlocks();
1028+ long blockSizeBytes = stat.getBlockSize();
1029+ long total = getTotalStorage(context, storage);
1030+
1031+ long avail = blocksAvailable * blockSizeBytes;
1032+ return total - avail - cacheSize;
1033+ }
1034+
1035+ public static long getTotalStorage(Context context, String storage) {
1036+ StatFs stat = new StatFs(getMusicDirectory(context));
1037+ long blocksCount = stat.getBlockCount();
1038+ long blockSizeBytes = stat.getBlockSize();
1039+ return blocksCount * blockSizeBytes;
1040+ }
1041+
1042+ public static String getFuzzySize(long size) {
1043+ String units = "B";
1044+ String formatString = "%.0f %s";
1045+ double s = size;
1046+ if (s > 1024.0) {
1047+ s /= 1024.0;
1048+ units = "KiB";
1049+ }
1050+ if (s > 1024.0) {
1051+ s /= 1024.0;
1052+ units = "MiB";
1053+ formatString = "%.02f %s";
1054+ }
1055+ if (s > 1024.0) {
1056+ s /= 1024.0;
1057+ units = "GiB";
1058+ }
1059+ return String.format(Locale.US, formatString, s, units);
1060+ }
1061+
1062+ public static class InvalidStorage extends Exception {
1063+ private static final long serialVersionUID = -3042684172473773638L;
1064+ public InvalidStorage() { super(); }
1065+ public InvalidStorage(String msg) { super(msg); }
1066+ }
1067+
1068+ public static class NoTertiaryStorage extends Exception {
1069+ private static final long serialVersionUID = -5605241960717201218L;
1070+ public NoTertiaryStorage() { super(); }
1071+ public NoTertiaryStorage(String msg) { super(msg); }
1072+ }
1073+}

Subscribers

People subscribed via source and target branches

to all changes: