Beautify Android 6 permission request
This commit is contained in:
parent
93e9567830
commit
2a3eb5f403
@ -151,7 +151,8 @@ THE SOFTWARE.
|
|||||||
<activity
|
<activity
|
||||||
android:name="FilebrowserStartActivity" />
|
android:name="FilebrowserStartActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name="PermissionRequestActivity" />
|
android:name="PermissionRequestActivity"
|
||||||
|
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||||
|
|
||||||
<activity android:name="AudioPickerActivity" android:theme="@style/DialogMinWidth"
|
<activity android:name="AudioPickerActivity" android:theme="@style/DialogMinWidth"
|
||||||
android:excludeFromRecents="true" android:exported="true" >
|
android:excludeFromRecents="true" android:exported="true" >
|
||||||
|
@ -42,5 +42,6 @@ THE SOFTWARE.
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="fill_parent" />
|
android:layout_height="fill_parent" />
|
||||||
</HorizontalScrollView>
|
</HorizontalScrollView>
|
||||||
|
<include layout="@layout/permission_request" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
42
res/layout/permission_request.xml
Normal file
42
res/layout/permission_request.xml
Normal 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>
|
@ -310,4 +310,6 @@ THE SOFTWARE.
|
|||||||
<string name="autoplaylist_playcounts_disabled">Do not create an automatic playlist</string>
|
<string name="autoplaylist_playcounts_disabled">Do not create an automatic playlist</string>
|
||||||
<string name="autoplaylist_playcounts_name" formatted="false">Top %d</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>
|
</resources>
|
||||||
|
@ -44,6 +44,11 @@ public class AudioPickerActivity extends PlaybackActivity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (PermissionRequestActivity.requestPermissions(this, intent)) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Uri uri = intent.getData();
|
Uri uri = intent.getData();
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
finish();
|
finish();
|
||||||
@ -120,7 +125,7 @@ public class AudioPickerActivity extends PlaybackActivity {
|
|||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
|
|
||||||
if (uri.getScheme().equals("content"))
|
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"))
|
if (uri.getScheme().equals("file"))
|
||||||
cursor = MediaUtils.getCursorForFileQuery(uri.getPath());
|
cursor = MediaUtils.getCursorForFileQuery(uri.getPath());
|
||||||
|
|
||||||
|
@ -135,6 +135,7 @@ public class LibraryActivity
|
|||||||
private TextView mArtist;
|
private TextView mArtist;
|
||||||
private ImageView mCover;
|
private ImageView mCover;
|
||||||
private View mEmptyQueue;
|
private View mEmptyQueue;
|
||||||
|
private View mPermissionRequest;
|
||||||
private MenuItem mSearchMenuItem;
|
private MenuItem mSearchMenuItem;
|
||||||
|
|
||||||
private HorizontalScrollView mLimiterScroller;
|
private HorizontalScrollView mLimiterScroller;
|
||||||
@ -195,6 +196,15 @@ public class LibraryActivity
|
|||||||
controls.setOnClickListener(this);
|
controls.setOnClickListener(this);
|
||||||
mActionControls = controls;
|
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();
|
loadTabOrder();
|
||||||
int page = settings.getInt(PrefKeys.LIBRARY_PAGE, PrefDefaults.LIBRARY_PAGE);
|
int page = settings.getInt(PrefKeys.LIBRARY_PAGE, PrefDefaults.LIBRARY_PAGE);
|
||||||
if (page != 0) {
|
if (page != 0) {
|
||||||
@ -524,6 +534,8 @@ public class LibraryActivity
|
|||||||
{
|
{
|
||||||
if (view == mCover || view == mActionControls) {
|
if (view == mCover || view == mActionControls) {
|
||||||
openPlaybackActivity();
|
openPlaybackActivity();
|
||||||
|
} else if (view == mPermissionRequest) {
|
||||||
|
PermissionRequestActivity.requestPermissions(this, getIntent());
|
||||||
} else if (view == mEmptyQueue) {
|
} else if (view == mEmptyQueue) {
|
||||||
setState(PlaybackService.get(this).setFinishAction(SongTimeline.FINISH_RANDOM));
|
setState(PlaybackService.get(this).setFinishAction(SongTimeline.FINISH_RANDOM));
|
||||||
} else if (view.getTag() != null) {
|
} else if (view.getTag() != null) {
|
||||||
@ -564,7 +576,7 @@ public class LibraryActivity
|
|||||||
ContentResolver resolver = getContentResolver();
|
ContentResolver resolver = getContentResolver();
|
||||||
Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
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 };
|
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 != null) {
|
||||||
if (cursor.moveToNext()) {
|
if (cursor.moveToNext()) {
|
||||||
String[] fields;
|
String[] fields;
|
||||||
|
@ -273,7 +273,7 @@ public class MediaUtils {
|
|||||||
{
|
{
|
||||||
String[] projection = { "_id" };
|
String[] projection = { "_id" };
|
||||||
Uri uri = CompatHoneycomb.getContentUriForAudioId((int)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 != null) {
|
||||||
if (cursor.moveToNext())
|
if (cursor.moveToNext())
|
||||||
@ -380,7 +380,7 @@ public class MediaUtils {
|
|||||||
Uri media = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
Uri media = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||||
String selection = MediaStore.Audio.Media.IS_MUSIC;
|
String selection = MediaStore.Audio.Media.IS_MUSIC;
|
||||||
selection += " AND length(_data)";
|
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) {
|
if (cursor == null) {
|
||||||
sSongCount = 0;
|
sSongCount = 0;
|
||||||
} else {
|
} else {
|
||||||
@ -404,7 +404,7 @@ public class MediaUtils {
|
|||||||
Uri media = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
Uri media = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||||
String selection = MediaStore.Audio.Media.IS_MUSIC;
|
String selection = MediaStore.Audio.Media.IS_MUSIC;
|
||||||
selection += " AND length(_data)";
|
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) {
|
if (cursor == null || cursor.getCount() == 0) {
|
||||||
sSongCount = 0;
|
sSongCount = 0;
|
||||||
return null;
|
return null;
|
||||||
@ -425,6 +425,30 @@ public class MediaUtils {
|
|||||||
return ids;
|
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()
|
public static void onMediaChange()
|
||||||
{
|
{
|
||||||
sSongCount = -1;
|
sSongCount = -1;
|
||||||
|
@ -30,23 +30,24 @@ import android.os.Bundle;
|
|||||||
|
|
||||||
public class PermissionRequestActivity extends Activity {
|
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 };
|
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)
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
ThemeHelper.setTheme(this, R.style.VanillaBase);
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
setTitle(R.string.app_name);
|
mCallbackIntent = getIntent().getExtras().getParcelable("callbackIntent");
|
||||||
// Fixme: This should probably be some welcome dialog with a button to launch askForPermissions
|
|
||||||
// setContentView(R.layout.showqueue_listview);
|
|
||||||
|
|
||||||
requestPermissions(NEEDED_PERMISSIONS, 0);
|
requestPermissions(NEEDED_PERMISSIONS, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by Activity after the user interacted with the permission request
|
* Called by Activity after the user interacted with the permission request
|
||||||
* Will launch the main activity if all permissions were granted, exits otherwise
|
* Will launch the main activity if all permissions were granted, exits otherwise
|
||||||
@ -63,31 +64,37 @@ public class PermissionRequestActivity extends Activity {
|
|||||||
grantedPermissions++;
|
grantedPermissions++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set as finished before (possibly) killing ourselfs
|
||||||
|
finish();
|
||||||
|
|
||||||
if (grantedPermissions == grantResults.length) {
|
if (grantedPermissions == grantResults.length) {
|
||||||
Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName());
|
if (mCallbackIntent != null) {
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
// start the old intent but ensure to make it a new task & clear any old attached activites
|
||||||
startActivity(intent);
|
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
|
// 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
|
// 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
|
// to get 'partial read' permissions (eg: reading from the content provider works
|
||||||
// but reading from /sdcard doesn't)
|
// but reading from /sdcard doesn't)
|
||||||
android.os.Process.killProcess(android.os.Process.myPid());
|
android.os.Process.killProcess(android.os.Process.myPid());
|
||||||
}
|
}
|
||||||
finish();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launches a permission request dialog if needed
|
* Launches a permission request dialog if needed
|
||||||
*
|
*
|
||||||
* @param activity The activitys context to use for the permission check
|
* @param activity The activitys context to use for the permission check
|
||||||
* @return boolean true if we showed a permission request dialog
|
* @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);
|
boolean havePermissions = havePermissions(activity);
|
||||||
|
|
||||||
if (havePermissions == false)
|
if (havePermissions == false) {
|
||||||
activity.startActivity(new Intent(activity, PermissionRequestActivity.class));
|
Intent intent = new Intent(activity, PermissionRequestActivity.class);
|
||||||
|
intent.putExtra("callbackIntent", callbackIntent);
|
||||||
|
activity.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
return !havePermissions;
|
return !havePermissions;
|
||||||
}
|
}
|
||||||
@ -98,7 +105,7 @@ public class PermissionRequestActivity extends Activity {
|
|||||||
* @param context The context to use
|
* @param context The context to use
|
||||||
* @return boolean true if all permissions have been granded
|
* @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) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
for (String permission : NEEDED_PERMISSIONS) {
|
for (String permission : NEEDED_PERMISSIONS) {
|
||||||
if (context.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
|
if (context.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
@ -98,8 +98,6 @@ public abstract class PlaybackActivity extends Activity
|
|||||||
mLooper = thread.getLooper();
|
mLooper = thread.getLooper();
|
||||||
mHandler = new Handler(mLooper, this);
|
mHandler = new Handler(mLooper, this);
|
||||||
|
|
||||||
if (PermissionRequestActivity.requestPermissions(this))
|
|
||||||
finish();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -47,7 +47,7 @@ public class Playlist {
|
|||||||
Uri media = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
|
Uri media = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
|
||||||
String[] projection = { MediaStore.Audio.Playlists._ID, MediaStore.Audio.Playlists.NAME };
|
String[] projection = { MediaStore.Audio.Playlists._ID, MediaStore.Audio.Playlists.NAME };
|
||||||
String sort = 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;
|
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 },
|
new String[] { MediaStore.Audio.Playlists._ID },
|
||||||
MediaStore.Audio.Playlists.NAME + "=?",
|
MediaStore.Audio.Playlists.NAME + "=?",
|
||||||
new String[] { name }, null);
|
new String[] { name }, null);
|
||||||
@ -163,7 +163,7 @@ public class Playlist {
|
|||||||
// Find the greatest PLAY_ORDER in the playlist
|
// Find the greatest PLAY_ORDER in the playlist
|
||||||
Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
|
Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
|
||||||
String[] projection = new String[] { MediaStore.Audio.Playlists.Members.PLAY_ORDER };
|
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;
|
int base = 0;
|
||||||
if (cursor.moveToLast())
|
if (cursor.moveToLast())
|
||||||
base = cursor.getInt(0) + 1;
|
base = cursor.getInt(0) + 1;
|
||||||
|
@ -73,6 +73,6 @@ public class QueryTask {
|
|||||||
*/
|
*/
|
||||||
public Cursor runQuery(ContentResolver resolver)
|
public Cursor runQuery(ContentResolver resolver)
|
||||||
{
|
{
|
||||||
return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
|
return MediaUtils.queryResolver(resolver, uri, projection, selection, selectionArgs, sortOrder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -346,7 +346,7 @@ public final class SongTimeline {
|
|||||||
ContentResolver resolver = mContext.getContentResolver();
|
ContentResolver resolver = mContext.getContentResolver();
|
||||||
Uri media = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
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 != null) {
|
||||||
if (cursor.getCount() != 0) {
|
if (cursor.getCount() != 0) {
|
||||||
cursor.moveToNext();
|
cursor.moveToNext();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user