Beautify Android 6 permission request

This commit is contained in:
Adrian Ulrich 2015-11-01 17:09:37 +01:00
parent 93e9567830
commit 2a3eb5f403
12 changed files with 121 additions and 29 deletions

View File

@ -151,7 +151,8 @@ THE SOFTWARE.
<activity
android:name="FilebrowserStartActivity" />
<activity
android:name="PermissionRequestActivity" />
android:name="PermissionRequestActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<activity android:name="AudioPickerActivity" android:theme="@style/DialogMinWidth"
android:excludeFromRecents="true" android:exported="true" >

View File

@ -42,5 +42,6 @@ THE SOFTWARE.
android:layout_width="wrap_content"
android:layout_height="fill_parent" />
</HorizontalScrollView>
<include layout="@layout/permission_request" />
</LinearLayout>

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2015 Adrian Ulrich <adrian@blinkenlights.ch>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/permission_request"
android:visibility="gone"
android:clickable="true"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:stretchColumns="0"
android:shrinkColumns="0"
android:background="#FF212121">
<TableRow>
<TextView
android:id="@+id/permission_request_title"
android:layout_marginLeft="8dip"
android:layout_marginBottom="8dip"
android:layout_marginTop="8dip"
android:text="@string/permission_request_summary"
android:textColor="#FFFFFFFF" />
<Button
android:id="@+id/permission_request_button"
android:clickable="false"
android:layout_marginRight="8dip"
android:layout_gravity="center"
android:text="@string/ok" />
</TableRow>
</TableLayout>

View File

@ -310,4 +310,6 @@ THE SOFTWARE.
<string name="autoplaylist_playcounts_disabled">Do not create an automatic playlist</string>
<string name="autoplaylist_playcounts_name" formatted="false">Top %d</string>
<string name="permission_request_summary">Vanilla Music needs read permission to display your music library</string>
<string name="ok">Ok</string>
</resources>

View File

@ -44,6 +44,11 @@ public class AudioPickerActivity extends PlaybackActivity {
return;
}
if (PermissionRequestActivity.requestPermissions(this, intent)) {
finish();
return;
}
Uri uri = intent.getData();
if (uri == null) {
finish();
@ -120,7 +125,7 @@ public class AudioPickerActivity extends PlaybackActivity {
Cursor cursor = null;
if (uri.getScheme().equals("content"))
cursor = getContentResolver().query(uri, Song.FILLED_PROJECTION, null, null, null);
cursor = MediaUtils.queryResolver(getContentResolver(), uri, Song.FILLED_PROJECTION, null, null, null);
if (uri.getScheme().equals("file"))
cursor = MediaUtils.getCursorForFileQuery(uri.getPath());

View File

@ -135,6 +135,7 @@ public class LibraryActivity
private TextView mArtist;
private ImageView mCover;
private View mEmptyQueue;
private View mPermissionRequest;
private MenuItem mSearchMenuItem;
private HorizontalScrollView mLimiterScroller;
@ -195,6 +196,15 @@ public class LibraryActivity
controls.setOnClickListener(this);
mActionControls = controls;
mPermissionRequest = (View)findViewById(R.id.permission_request);
if(PermissionRequestActivity.havePermissions(this) == false) {
// We are lacking permissions: bind and display nag bar
mPermissionRequest.setOnClickListener(this);
mPermissionRequest.setVisibility(View.VISIBLE);
}
loadTabOrder();
int page = settings.getInt(PrefKeys.LIBRARY_PAGE, PrefDefaults.LIBRARY_PAGE);
if (page != 0) {
@ -524,6 +534,8 @@ public class LibraryActivity
{
if (view == mCover || view == mActionControls) {
openPlaybackActivity();
} else if (view == mPermissionRequest) {
PermissionRequestActivity.requestPermissions(this, getIntent());
} else if (view == mEmptyQueue) {
setState(PlaybackService.get(this).setFinishAction(SongTimeline.FINISH_RANDOM));
} else if (view.getTag() != null) {
@ -564,7 +576,7 @@ public class LibraryActivity
ContentResolver resolver = getContentResolver();
Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String[] projection = new String[] { MediaStore.Audio.Media.ARTIST_ID, MediaStore.Audio.Media.ALBUM_ID, MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM };
Cursor cursor = resolver.query(uri, projection, selection, null, null);
Cursor cursor = MediaUtils.queryResolver(resolver, uri, projection, selection, null, null);
if (cursor != null) {
if (cursor.moveToNext()) {
String[] fields;

View File

@ -273,7 +273,7 @@ public class MediaUtils {
{
String[] projection = { "_id" };
Uri uri = CompatHoneycomb.getContentUriForAudioId((int)id);
Cursor cursor = resolver.query(uri, projection, null, null, null);
Cursor cursor = queryResolver(resolver, uri, projection, null, null, null);
if (cursor != null) {
if (cursor.moveToNext())
@ -380,7 +380,7 @@ public class MediaUtils {
Uri media = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String selection = MediaStore.Audio.Media.IS_MUSIC;
selection += " AND length(_data)";
Cursor cursor = resolver.query(media, new String[]{"count(_id)"}, selection, null, null);
Cursor cursor = queryResolver(resolver, media, new String[]{"count(_id)"}, selection, null, null);
if (cursor == null) {
sSongCount = 0;
} else {
@ -404,7 +404,7 @@ public class MediaUtils {
Uri media = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String selection = MediaStore.Audio.Media.IS_MUSIC;
selection += " AND length(_data)";
Cursor cursor = resolver.query(media, Song.EMPTY_PROJECTION, selection, null, null);
Cursor cursor = queryResolver(resolver, media, Song.EMPTY_PROJECTION, selection, null, null);
if (cursor == null || cursor.getCount() == 0) {
sSongCount = 0;
return null;
@ -425,6 +425,30 @@ public class MediaUtils {
return ids;
}
/**
* Runs a query on the passed content resolver.
* Catches (and returns null on) SecurityException (= user revoked read permission)
*
* @param resolver The content resolver to use
* @param uri the uri to query
* @param projection the projection to use
* @param selection the selection to use
* @param selectionArgs arguments for the selection
* @param sortOrder sort order of the returned result
*
* @return a cursor or null
*/
public static Cursor queryResolver(ContentResolver resolver, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
{
Cursor cursor = null;
try {
cursor = resolver.query(uri, projection, selection, selectionArgs, sortOrder);
} catch(java.lang.SecurityException e) {
// we do not have read permission - just return a null cursor
}
return cursor;
}
public static void onMediaChange()
{
sSongCount = -1;

View File

@ -30,23 +30,24 @@ import android.os.Bundle;
public class PermissionRequestActivity extends Activity {
// 'dangerous' permissions not granted by the manifest on versions >= M
/**
* 'dangerous' permissions not granted by the manifest on versions >= M
*/
private static final String[] NEEDED_PERMISSIONS = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE };
/**
* The intent to start after acquiring the required permissions
*/
private Intent mCallbackIntent;
@TargetApi(Build.VERSION_CODES.M)
@Override
public void onCreate(Bundle savedInstanceState) {
ThemeHelper.setTheme(this, R.style.VanillaBase);
super.onCreate(savedInstanceState);
setTitle(R.string.app_name);
// Fixme: This should probably be some welcome dialog with a button to launch askForPermissions
// setContentView(R.layout.showqueue_listview);
mCallbackIntent = getIntent().getExtras().getParcelable("callbackIntent");
requestPermissions(NEEDED_PERMISSIONS, 0);
}
/**
* Called by Activity after the user interacted with the permission request
* Will launch the main activity if all permissions were granted, exits otherwise
@ -63,31 +64,37 @@ public class PermissionRequestActivity extends Activity {
grantedPermissions++;
}
// set as finished before (possibly) killing ourselfs
finish();
if (grantedPermissions == grantResults.length) {
Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
if (mCallbackIntent != null) {
// start the old intent but ensure to make it a new task & clear any old attached activites
mCallbackIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(mCallbackIntent);
}
// Hack: We *kill* ourselfs (while launching the main activity) to get startet
// in a new process: This works around a bug/feature in 6.0 that would cause us
// to get 'partial read' permissions (eg: reading from the content provider works
// but reading from /sdcard doesn't)
android.os.Process.killProcess(android.os.Process.myPid());
}
finish();
}
/**
* Launches a permission request dialog if needed
*
* @param activity The activitys context to use for the permission check
* @return boolean true if we showed a permission request dialog
*/
public static boolean requestPermissions(Activity activity) {
public static boolean requestPermissions(Activity activity, Intent callbackIntent) {
boolean havePermissions = havePermissions(activity);
if (havePermissions == false)
activity.startActivity(new Intent(activity, PermissionRequestActivity.class));
if (havePermissions == false) {
Intent intent = new Intent(activity, PermissionRequestActivity.class);
intent.putExtra("callbackIntent", callbackIntent);
activity.startActivity(intent);
}
return !havePermissions;
}
@ -98,7 +105,7 @@ public class PermissionRequestActivity extends Activity {
* @param context The context to use
* @return boolean true if all permissions have been granded
*/
private static boolean havePermissions(Context context) {
public static boolean havePermissions(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
for (String permission : NEEDED_PERMISSIONS) {
if (context.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {

View File

@ -98,8 +98,6 @@ public abstract class PlaybackActivity extends Activity
mLooper = thread.getLooper();
mHandler = new Handler(mLooper, this);
if (PermissionRequestActivity.requestPermissions(this))
finish();
}
@Override

View File

@ -47,7 +47,7 @@ public class Playlist {
Uri media = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
String[] projection = { MediaStore.Audio.Playlists._ID, MediaStore.Audio.Playlists.NAME };
String sort = MediaStore.Audio.Playlists.NAME;
return resolver.query(media, projection, null, null, sort);
return MediaUtils.queryResolver(resolver, media, projection, null, null, sort);
}
/**
@ -79,7 +79,7 @@ public class Playlist {
{
long id = -1;
Cursor cursor = resolver.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
Cursor cursor = MediaUtils.queryResolver(resolver, MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
new String[] { MediaStore.Audio.Playlists._ID },
MediaStore.Audio.Playlists.NAME + "=?",
new String[] { name }, null);
@ -163,7 +163,7 @@ public class Playlist {
// Find the greatest PLAY_ORDER in the playlist
Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
String[] projection = new String[] { MediaStore.Audio.Playlists.Members.PLAY_ORDER };
Cursor cursor = resolver.query(uri, projection, null, null, null);
Cursor cursor = MediaUtils.queryResolver(resolver, uri, projection, null, null, null);
int base = 0;
if (cursor.moveToLast())
base = cursor.getInt(0) + 1;

View File

@ -73,6 +73,6 @@ public class QueryTask {
*/
public Cursor runQuery(ContentResolver resolver)
{
return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
return MediaUtils.queryResolver(resolver, uri, projection, selection, selectionArgs, sortOrder);
}
}

View File

@ -346,7 +346,7 @@ public final class SongTimeline {
ContentResolver resolver = mContext.getContentResolver();
Uri media = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = resolver.query(media, Song.FILLED_PROJECTION, selection.toString(), null, "_id");
Cursor cursor = MediaUtils.queryResolver(resolver, media, Song.FILLED_PROJECTION, selection.toString(), null, "_id");
if (cursor != null) {
if (cursor.getCount() != 0) {
cursor.moveToNext();