I have an issue with my Android App. I am asking for permissions to the user to access the READ_EXTERNAL_STORAGE but the bug "permission denial" still makes my app crash.
I have spent hours on it and I have no clue how to fix it.
Here's my logcat:
03-07 15:02:12.387 6800-8893/? E/DatabaseUtils: Writing exception to parcel
java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider uri content://media/external/images/media from pid=8108, uid=10089 requires android.permission.READ_EXTERNAL_STORAGE, or grantUriPermission()
at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:605)
at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:480)
at android.content.ContentProvider$Transport.query(ContentProvider.java:211)
at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:112)
at android.os.Binder.execTransact(Binder.java:453)
03-07 15:02:12.391 8108-11729/? E/iu.UploadsManager: Insufficient permissions to process media
java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider uri content://media/external/images/media from pid=8108, uid=10089 requires android.permission.READ_EXTERNAL_STORAGE, or grantUriPermission()
at android.os.Parcel.readException(Parcel.java:1602)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:183)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:135)
at android.content.ContentProviderProxy.query(ContentProviderNative.java:421)
at android.content.ContentResolver.query(ContentResolver.java:502)
at android.content.ContentResolver.query(ContentResolver.java:438)
at lqt.a(PG:14)
at com.google.android.libraries.social.autobackup.FingerprintScannerIntentService.onHandleIntent(PG:15)
at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:66)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:150)
at android.os.HandlerThread.run(HandlerThread.java:61)
My Main activity is:
package com.example.arjufy;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.Manifest;
import android.support.v4.app.ActivityCompat;
import android.widget.Toast;
import android.content.Intent;
import android.support.v4.app.Fragment;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
public class MainActivity extends AppCompatActivity implements MarketPlaceFragment.OnListFragmentInteractionListener {
private FirebaseAuth mFirebaseAuth;
private FirebaseUser mFirebaseUser;
private DatabaseReference mDatabaseReference;
private String mUsername;
private String mPhotoUrl;
public static final int MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 123;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (checkPermissionREAD_EXTERNAL_STORAGE(this)) {
// do your stuff..
// Initialize Firebase Auth
mFirebaseAuth = FirebaseAuth.getInstance();
mFirebaseUser = mFirebaseAuth.getCurrentUser();
if (mFirebaseUser == null) {
// Not signed in, launch the Sign In activity
startActivity(new Intent(this, SplashScreenActivity.class));
finish();
return;
} else {
mUsername = mFirebaseUser.getEmail();
}
mDatabaseReference = FirebaseDatabase.getInstance().getReference();
setContentView(R.layout.activity_main);
Fragment fragment = new MarketPlaceFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.theFragmentFrame, fragment).addToBackStack("MarketPlaceFragment").commit();
}
}
public boolean checkPermissionREAD_EXTERNAL_STORAGE(
final Context context) {
int currentAPIVersion = Build.VERSION.SDK_INT;
if (currentAPIVersion >= android.os.Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(context,
Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(
(Activity) context,
Manifest.permission.READ_EXTERNAL_STORAGE)) {
showDialog("External storage", context,
Manifest.permission.READ_EXTERNAL_STORAGE);
} else {
ActivityCompat
.requestPermissions(
(Activity) context,
new String[] { Manifest.permission.READ_EXTERNAL_STORAGE },
MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);
}
return false;
} else {
return true;
}
} else {
return true;
}
}
public void showDialog(final String msg, final Context context,
final String permission) {
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(context);
alertBuilder.setCancelable(true);
alertBuilder.setTitle("Permission necessary");
alertBuilder.setMessage(msg + " permission is necessary");
alertBuilder.setPositiveButton(android.R.string.yes,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions((Activity) context,
new String[] { permission },
MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);
}
});
AlertDialog alert = alertBuilder.create();
alert.show();
}
#Override
public void onRequestPermissionsResult(int requestCode,
String[] permissions, int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// do your stuff
} else {
Toast.makeText(MainActivity.this, "GET_ACCOUNTS Denied",
Toast.LENGTH_SHORT).show();
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions,
grantResults);
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case R.id.logoff:
FirebaseAuth.getInstance().signOut();
Intent intent1 = new Intent(this, LoginActivity.class);
startActivity(intent1);
return true;
case R.id.profile:
Intent intent2 = new Intent(this, MyProfileActivity.class);
startActivity(intent2);
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public void onListFragmentInteraction(DatabaseReference reference) {
Intent intent = new Intent(this, ProductDetailViewActivity.class);
intent.putExtra("Product reference", reference.toString());
startActivity(intent);
}
}
My manifest is:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.arjufy">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-feature
android:name="android.hardware.camera"
android:required="true" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>``
<activity android:name=".LoginActivity" />
<activity android:name=".SplashScr
eenActivity" />
<activity android:name=".AddProductActivity" />
<activity android:name=".SignUpActivity" />
<activity android:name=".MyProfileActivity" />
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.arjufy"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/path_files" />
</provider>
<activity android:name=".ProductDetailViewActivity" />
</application>
</manifest>
and my gradle is:
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.arjufy"
minSdkVersion 23
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.google.firebase:firebase-auth:16.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.google.android.gms:play-services-auth:16.0.1'
implementation 'com.google.firebase:firebase-storage:16.1.0'
implementation 'com.google.firebase:firebase-database:16.1.0'
implementation 'com.firebaseui:firebase-ui:0.5.3'
implementation 'me.relex:circleindicator:2.1.0#aar'
implementation 'com.getbase:floatingactionbutton:1.10.1'
implementation 'de.hdodenhof:circleimageview:2.2.0'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'com.google.firebase:firebase-core:16.0.7'
}
Do you have any idea where it might come from?
Thanks a lot for your help.
Very much appreciated.
From Android Developer Documentation
If your app needs a dangerous permission, you must check whether you have that permission every time you perform an operation that requires that permission. Beginning with Android 6.0 (API level 23), users can revoke permissions from any app at any time, even if the app targets a lower API level.
So you need to request permission like this
#TargetApi(23)
public void enableRunTimePermisstion() {
if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) &&
(getActivity().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) !=
PackageManager.PERMISSION_GRANTED))
if (getActivity().shouldShowRequestPermissionRationale
(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
Toast.makeText(getActivity(), "Write storage permission is need for app"
, Toast.LENGTH_LONG).show();
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
} else {
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
Toast.makeText(getActivity(), "request permission"
, Toast.LENGTH_LONG).show();
}
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1) {
if (permissions[0].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE) &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(context, "Write external storage granted", Toast.LENGTH_LONG).show();
// >> here you call the method that need the permission
}
} else {
Toast.makeText(getActivity(), "Write external permission denied", Toast.LENGTH_SHORT).show();
enableRunTimePermisstion(); // >> When user request the permission we calling the previous method again.
}
}
Runtime permission has actually taken a new shape in the recent times ... like you need to use a fileprovider, provide Uri and the likes but here is a good library that i have used in the recent times. It works well and hassle free. You can check it out on codepath.
https://guides.codepath.com/android/Managing-Runtime-Permissions-with-PermissionsDispatcher
Cheers.
Related
after tinkering with the paths XML and my manifest provider settings I was finally able to get my app to stop crashing when trying to send an attachment in an email intent.
HOWEVER, while everything seems normal in the app, when Gmail or drive opens the file is not attached.
Screenshots:
My code is as follows
MainActivity.java
package com.loopbreakr.filesend;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import java.io.File;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
public String reciever;
public String subject;
public String body;
public final String stringPath = "/storage/emulated/0/Android/data/com.loopbreakr.firstpdf/files/PDF_files/Abdile&Name 2021-01-29&15:59:55.pdf";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
reciever = "mkercode#gmail.com";
subject = "my subject";
body = "blank email";
File file = new File(stringPath);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
prepareEmail(file);
}
});
}
private void prepareEmail(File report) {
ArrayList<Uri> uris = new ArrayList<>();
uris.add(FileProvider.getUriForFile(getApplicationContext(), "com.loopbreakr.filesend", report));
Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
intent.setType("message/rfc822");
intent.putExtra(Intent.EXTRA_EMAIL, reciever);
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
intent.putExtra(Intent.EXTRA_TEXT, body);
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(intent, "Send email via:").addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION));
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.loopbreakr.filesend">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.Filesend">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.loopbreakr.filesend"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
<meta-data
android:name="com.google.android.actions"
android:resource="#xml/file_paths" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external"
path="." />
<external-files-path
name="external_files"
path="." />
<cache-path
name="cache"
path="." />
<external-cache-path
name="external_cache"
path="." />
<files-path
name="files"
path="." />
</paths>
As the program can find my file, I don't think it's a permission error anymore. The file is also on external storage. Is it possible that I am missing something in the intent? Many thanks!
EDITED
Note that I originally manually set the permissions to make my question more readable, however after adding runtime storage reading permissions to my code and simplifying the filename, as well as changing the intent to only send one file I get the couldn't attach file toast message
MainActivity.java:
package com.loopbreakr.filesend;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import android.Manifest;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.io.File;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
public String reciever;
public String subject;
public String body;
public final String stringPath = "/storage/emulated/0/Android/data/samplefile.pdf";
private int STORAGE_PERMISSION_CODE = 1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
reciever = "mkercode#gmail.com";
subject = "my subject";
body = "blank email";
File file = new File(stringPath);
Button button = findViewById(R.id.button);
if (ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(MainActivity.this, "You have already granted this permission!",
Toast.LENGTH_SHORT).show();
} else {
requestStoragePermission();
}
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
prepareEmail(file);
}
});
}
private void prepareEmail(File report) {
Uri uri = FileProvider.getUriForFile(getApplicationContext(), "com.loopbreakr.filesend", report);
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("message/rfc822");
intent.putExtra(Intent.EXTRA_EMAIL, reciever);
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
intent.putExtra(Intent.EXTRA_TEXT, body);
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Intent chooser = Intent.createChooser(intent, "Share File");
List<ResolveInfo> resInfoList = this.getPackageManager().queryIntentActivities(chooser, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
this.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
startActivity(chooser);
}
private void requestStoragePermission() {
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.READ_EXTERNAL_STORAGE)) {
new AlertDialog.Builder(this)
.setTitle("Permission needed")
.setMessage("This permission is needed because of this and that")
.setPositiveButton("ok", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(MainActivity.this,
new String[] {Manifest.permission.READ_EXTERNAL_STORAGE}, STORAGE_PERMISSION_CODE);
}
})
.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.create().show();
} else {
ActivityCompat.requestPermissions(this,
new String[] {Manifest.permission.READ_EXTERNAL_STORAGE}, STORAGE_PERMISSION_CODE);
}
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
if (requestCode == STORAGE_PERMISSION_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "Permission GRANTED", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Permission DENIED", Toast.LENGTH_SHORT).show();
}
}
}
}
Am I missing something in my manifest?
I tried to send PDF attachment with ACTION_SEND_MULTIPLE and I found your problem is that you need to grant the explicit permission to email Intent (not to the chooser Intent as you do). My code:
Intent emailIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);//ACTION_SEND does not support purParcelableArrayListExtra
emailIntent.setType("text/plain");
emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{testBox.getEmail()});
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Covid Test Certificate result");
emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);//attaching the pdf file(s) to the email
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//does not really work right now, I had to give explicit permissions
//GRANTING THE PERMISSIONS EXPLICITLY HERE! to all possible choosers (3rd party apps):
List<ResolveInfo> resolvedInfoActivities =
activity.getPackageManager().queryIntentActivities(emailIntent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo ri : resolvedInfoActivities) {
for (Uri uri : uris) {
Log.d(TAG, "Granting permission to - " + ri.activityInfo.packageName);
activity.grantUriPermission(ri.activityInfo.packageName,uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
try {
Intent chooserIntent =Intent.createChooser(emailIntent, "Send mail...").addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
activity.startActivity(
chooserIntent
);
} catch (android.content.ActivityNotFoundException ex) {
Toast.makeText(activity, "There are no email clients installed.", Toast.LENGTH_SHORT).show();
Log.e(TAG, "ERROR, THERE ARE NO EMAIL CLIENTS INSTALLED.");
}
And for others wondering about the permissions, this is a good article:
https://medium.com/#benexus/dealing-with-permissions-when-sharing-files-android-m-cee9ecc287bf
It says the explicit permissions are needed, because the permissions added via provider in Manifest and via Intent do not work.
I am making a cleaner version of a research drone android app that utilises DJI SDK/Hardware and attempting to structure the app more appropriately such that it can be expanded later. The application allows user selection of hardware (currently mock/test, DJI and ToDo) from a config pop up in the main display (class UserInterface extends AppCompatActivity). This is to allow the app to be used without registering or check perms when it is not being used for DJI. Previously I registered the DJI SDK when the app first opens, but it was my first real app and was a mess to follow its flow and didn't manage screen real estate very well.
When the DJI option is actioned from the pop up "config" window on the main interface from UI class, the UI class saves the config to a SettingsManager instance and calls the EventsManager instance with method runSettings(). The method actions the relevant SDK by try/catch{new DJI SDK class} and passes both the instances of EventsManager and UI.
The difference of this major version compared to my first version (which functions correctly) is the API key (changed package name, so new key), I am not doing the SDK registration in the first main activity and the SDK registration is not a viewable class, it only forwards strings to run on UI thread for showToast method in the UI class.
App does not return any errors.
showToast for messages in the DJI SDK class appear on the display but nothing inside the
SDKManagerCallback occurs.
Unsure of constructor in a class with extends AppCompatActivity, context methods seem unavailable when
tried with different class extensions.
I have tried creating two DJI developer keys, no difference.
Unsure of DJI class listing in Manifest as it is not an activity started by an intent with bundle but
does require context.
DJI SDK class is extracted from DJISDKDemo (I am fairly new to android app development, not sure I
could write my own version from the limited explanation on DJI dev site).
Thank you greatly for your help and any other tips appreciated.
The SDKRegistration class:
package com.research.droneapp; // mock package name for SO
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import dji.common.error.DJIError;
import dji.common.error.DJISDKError;
import dji.sdk.base.BaseComponent;
import dji.sdk.base.BaseProduct;
import dji.sdk.sdkmanager.DJISDKInitEvent;
import dji.sdk.sdkmanager.DJISDKManager;
public class SoftwareDevelopmentKitDJI extends AppCompatActivity {
private static final String TAG = "SoftwareDevelopmentKit";
public UserInterface userInterface;
public EventsManager eventsManager;
public static final String FLAG_CONNECTION_CHANGE = "dji_sdk_connection_change";
private static BaseProduct mProduct;
private Handler mHandler;
public boolean isConnected = false; // Variable for start up flag
private static final String[] REQUIRED_PERMISSION_LIST = new String[]{
Manifest.permission.VIBRATE,
Manifest.permission.INTERNET,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.WAKE_LOCK,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_NETWORK_STATE,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.READ_PHONE_STATE,
};
private List<String> missingPermission = new ArrayList<>();
private AtomicBoolean isRegistrationInProgress = new AtomicBoolean(false);
private static final int REQUEST_PERMISSION_CODE = 12345;
public SoftwareDevelopmentKitDJI(UserInterface userInterface, EventsManager eventsManager) {
Log.d(TAG, "SoftwareDevelopmentKitDJI");
this.userInterface = userInterface;
this.eventsManager = eventsManager;
// Permission checked true actions SDK registration
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
checkAndRequestPermissions();
}
// Handle DJI SDK hardware changes in background thread??
// ToDo: Receive hardware changes in EventsManager
mHandler = new Handler(Looper.getMainLooper());
}
/**
* Checks if there is any missing permissions, and
* requests runtime permission if needed.
*/
private void checkAndRequestPermissions() {
Log.d(TAG, "checkAndRequestPermissions: S");
// Check for permissions
for (String eachPermission : REQUIRED_PERMISSION_LIST) {
if (ContextCompat.checkSelfPermission(userInterface, eachPermission) !=
PackageManager.PERMISSION_GRANTED) {
missingPermission.add(eachPermission);
}
}
// Request for missing permissions
if (missingPermission.isEmpty()) {
Log.d(TAG, "notMissingPerms");
startSDKRegistration();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Log.d(TAG, "missingPerms");
passToastToUI("Need permissions!");
ActivityCompat.requestPermissions(userInterface,
missingPermission.toArray(new String[missingPermission.size()]),
REQUEST_PERMISSION_CODE);
}
Log.d(TAG, "checkAndRequestPermissions: E");
}
/**
* Result of runtime permission request
*/
#Override
public void onRequestPermissionsResult(int requestCode,
#NonNull String[] permissions,
#NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// Check for granted permission and remove from missing list
if (requestCode == REQUEST_PERMISSION_CODE) {
for (int i = grantResults.length - 1; i >= 0; i--) {
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
missingPermission.remove(permissions[i]);
}
}
}
// If no missing permission, start SDK registration
if (missingPermission.isEmpty()) {
startSDKRegistration();
} else {
passToastToUI("Missing permissions!!!");
}
}
private void startSDKRegistration() {
Log.d(TAG, "startSDKRegistration: S");
if (isRegistrationInProgress.compareAndSet(false, true)) {
AsyncTask.execute(new Runnable() {
#Override
public void run() {
passToastToUI("registering, pls wait...");
Log.d(TAG, "startSDKRegistration: run");
// ToDO: Investigate why SDKManagerCallback's don't occur
// (is getApplicationContext() correct?)
DJISDKManager.getInstance().registerApp(getApplicationContext(),
new DJISDKManager.SDKManagerCallback() {
#Override
public void onRegister(DJIError djiError) {
Log.d(TAG, "onRegister: S");
if (djiError == DJISDKError.REGISTRATION_SUCCESS) {
passToastToUI("Register Success");
DJISDKManager.getInstance().startConnectionToProduct();
}
else {
passToastToUI("Register sdk failed!");
}
Log.v(TAG, djiError.getDescription());
Log.d(TAG, "onRegister: E");
}
#Override
public void onProductDisconnect() {
Log.d(TAG, "onProductDisconnect");
passToastToUI("Product Disconnected");
notifyStatusChange();
isConnected = false; // Set hardware connection flag
}
#Override
public void onProductConnect(BaseProduct baseProduct) {
Log.d(TAG, String.format("onProductConnect newProduct:%s", baseProduct));
passToastToUI("Product Connected");
notifyStatusChange();
isConnected = true; // Set hardware connection flag
}
#Override
public void onComponentChange(BaseProduct.ComponentKey componentKey, BaseComponent oldComponent,
BaseComponent newComponent) {
if (newComponent != null) {
newComponent.setComponentListener(new BaseComponent.ComponentListener() {
#Override
public void onConnectivityChange(boolean isConnected) {
Log.d(TAG, "onComponentConnectivityChanged: " + isConnected);
notifyStatusChange();
}
});
}
Log.d(TAG, String.format(
"onComponentChange key:%s, oldComponent:%s, newComponent:%s",
componentKey, oldComponent, newComponent));
}
#Override
public void onInitProcess(DJISDKInitEvent djisdkInitEvent, int i) {
Log.d(TAG, "startSDKRegistration: onInitProcess");
}
#Override
public void onDatabaseDownloadProgress(long l, long l1) {
Log.d(TAG, "startSDKRegistration: onDatabaseDownloadProgress");
}
});
}
});
}
Log.d(TAG, "startSDKRegistration: E");
}
private void notifyStatusChange() {
mHandler.removeCallbacks(updateRunnable);
mHandler.postDelayed(updateRunnable, 500);
}
private Runnable updateRunnable = () -> {
Intent intent = new Intent(FLAG_CONNECTION_CHANGE);
sendBroadcast(intent);
};
private void passToastToUI(String toastMsg) {
runOnUiThread(() -> {
userInterface.showToast(toastMsg);
});
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.research.droneapp"> <!-- mock package name for SO -->
<!-- Permissions and features -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature
android:name="android.hardware.usb.host"
android:required="false" />
<uses-feature
android:name="android.hardware.usb.accessory"
android:required="true" />
<!-- App Activity Process -->
<application
android:name="com.research.droneapp.MApplication"
android:allowBackup="true"
android:icon="#mipmap/hmu_icon"
android:label="#string/app_name"
android:roundIcon="#mipmap/hmu_icon_round"
android:supportsRtl="true"
android:theme="#style/Theme.AppCompat.NoActionBar">
<!-- Main Display -->
<activity android:name="com.research.droneapp.UserInterface" />
<!-- DJI SDK -->
<uses-library android:name="com.android.future.usb.accessory" />
<meta-data
android:name="com.dji.sdk.API_KEY"
android:value="*************" /> <!-- removed for SO -->
<activity
android:name="dji.sdk.sdkmanager.DJIAoaControllerActivity"
android:theme="#android:style/Theme.Translucent">
<intent-filter>
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
android:resource="#xml/accessory_filter" />
</activity>
<service android:name="dji.sdk.sdkmanager.DJIGlobalService">
</service>
<activity android:name="com.research.droneapp.SoftwareDevelopmentKitDJI" />
<!-- Splash Screen at Launch -->
<activity android:name="com.research.droneapp.Splash" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
The method that starts the SDK class:
/**
* Settings changes to start relevant classes
*/
public void runSettings() {
Log.d(TAG, "runSettings: S");
// Get hardware selection from settings
int hardwarePosition = settingsManager.getHardwareInt();
/*ToDo: Set Test Environment*/
if (hardwarePosition == 0) { // Operate settings for test environment
Log.d(TAG, "runSettings: hardPos Test");
userInterface.showToast("runSetting: TEST");
}
else if (hardwarePosition == 1) { // Operate settings for DJI
Log.d(TAG, "runSettings: hardPos MavP2");
try {
this.softwareDevelopmentKitDJI = new SoftwareDevelopmentKitDJI(
userInterface, this);
Log.d(TAG, "runSettings: DJI Launched");
userInterface.showToast("runSetting: DJI");
} catch (Exception e) {
Log.d(TAG, "runSettings: DJI Error: "+ e.toString());
userInterface.showToast("runSetting: Error");
}
} // Operate settings for...?
else if (hardwarePosition == 2) { /*ToDo*/
Log.d(TAG, "runSettings: hardPos ToDo");
userInterface.showToast("runSetting: ToDo");
}
else { // Unknown hardware
Log.d(TAG, "runSettings: Error");
userInterface.showToast("runSetting:Error");
}
Log.d(TAG, "runSettings: E");
}
The config window click listener for action changes button, within the method of UI class for config pop up window:
// Action changes
Button btnAction = popupView.findViewById(R.id.btnAction);
btnAction.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d(TAG, "displayConfigOverlay: onClick -> configAction");
// Update hardware setting
String hardware =
(btnTest.isChecked())?settingsManager.HARDWARE_OPTIONS[0]:
(btnMavP2.isChecked())?settingsManager.HARDWARE_OPTIONS[1]:
(btnToDo.isChecked())?settingsManager.HARDWARE_OPTIONS[2]:
"NoSelection!";
settingsManager.setHardware(hardware);
// Update port number
String port = serverPortEdit.getText().toString();
settingsManager.setServerPort(port);
// Update WSS launch setting
boolean autoLunchWSS = swAutoWSS.isChecked();
settingsManager.setAutoLaunchWSS(autoLunchWSS);
// Display to user
showToast("Hardware: " + hardware + "\nPort: " + port + "\nAutoWSS: " +
autoLunchWSS);
// Push settings
eventsManager.runSettings();
// Close config pop up
popupConfig.dismiss();
}
});
Apologises for the lack of and mixed commenting styles. Again any tips appreciated, still new to android and java.
Min API23 setting in gradle allowed code to run but did not allow SDK to download. Unsure why errors not returned.
Solved by changing gradle to following:
...
android {
compileSdkVersion 29
buildToolsVersion '28.0.3'
useLibrary 'org.apache.http.legacy'
defaultConfig {
minSdkVersion 19
targetSdkVersion 29
multiDexEnabled true
ndk {
abiFilters 'armeabi-v7a', 'x86', 'arm64-v8a'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
...
Additionally found a potential context issue within layout, tools:context="..." was not set to the Activity interacting with this layout.
...
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/mainConstraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".UserInterface" >
...
I'm a newbie to Android development, so apologies in advance if this is a stupid question.
I'm running a simple app that tracks the user's location and store it in a real-time db.
the app works perfectly fine on the emulator, but not for the real devices.
it gets slow with a message showing that "V/FA: Inactivity, disconnecting from service", and when it starts running, nothing is written to the database.
here's my manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.alice.locationfinder3">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
and my build.gradle:
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.alice.locationfinder3"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0-rc02'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
implementation 'com.google.firebase:firebase-messaging:12.0.1'
implementation 'com.google.firebase:firebase-database:12.0.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.google.android.gms:play-services:12.0.1'
}
apply plugin: 'com.google.gms.google-services'
Finally, my main activity:
package com.example.alice.locationfinder3;
import android.Manifest;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.location.Criteria;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import java.text.SimpleDateFormat;
import java.util.Date;
import static android.location.Criteria.ACCURACY_FINE;
public class MainActivity extends AppCompatActivity {
private LocationManager locationManager;
private LocationListener locationListener;
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference mDatabase = database.getReference();
double latitude; // latitude
double longitude; // longitude
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
locationManager = (LocationManager) this.getSystemService(LOCATION_SERVICE);
Criteria criteria = new Criteria();
criteria.setAccuracy(ACCURACY_FINE);
String bestProvider = locationManager.getBestProvider(criteria, true);
locationListener = new LocationListener() {
#Override
public void onLocationChanged(Location location) {
Log.d("Location: ", location.toString());
latitude = location.getLatitude();
longitude = location.getLongitude();
long time = location.getTime();
Date date = new Date(time);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String timestamp = sdf.format(date);
writeNewPoint(timestamp, longitude, latitude);
}
#Override
public void onStatusChanged(String s, int i, Bundle bundle) {
}
#Override
public void onProviderEnabled(String s) {
}
#Override
public void onProviderDisabled(String s) {
}
};
if(Build.VERSION.SDK_INT < 23)
{
locationManager.requestLocationUpdates(bestProvider, 0, 0, locationListener);
}
else {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION},1);
}
else {
locationManager.requestLocationUpdates(bestProvider, 0, 0, locationListener);
}
}
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
Criteria criteria;
criteria = new Criteria();
criteria.setAccuracy(ACCURACY_FINE);
String bestProvider = locationManager.getBestProvider(criteria, true);
if(grantResults.length>0 && grantResults[0]== PackageManager.PERMISSION_GRANTED)
{
if(ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
{
locationManager.requestLocationUpdates(bestProvider, 0, 0, locationListener);
}
}
}
private void writeNewPoint(String timestamp, double longitude, double latitude) {
Point point = new Point(longitude, latitude);
mDatabase.child("points").child(timestamp).setValue(point);
}
}
Snapshot from logcat:
09-02 16:05:11.150 28873-28906/com.example.alice.locationfinder3 V/FA: Inactivity, disconnecting from the service
09-02 16:05:11.170 28873-28958/com.example.alice.locationfinder3 I/FirebaseCrash: Sending crashes
09-02 16:05:31.195 28873-28873/com.example.alice.locationfinder3 D/Location:: Location[gps XX.7591,XX.6441 hAcc=64 et=+16h53m5s212ms alt=604.5306458863317 vel=0.14499298 bear=124.898796 vAcc=??? sAcc=??? bAcc=??? {Bundle[mParcelledData.dataSize=40]}]
0
Again, this works fine with the emulator, runs and stores to the db.
But it fails with real devices.
Thanks.
Try following these steps.The reasoning is explained in this post: V/FA: Inactivity, disconnecting from the service
Steps:
1)Uninstall the app from your mobile/emulator.
2)Then go to the File option in the main menubar in the android studio.
3)Then click on Invalidatecasha/restart.
I am following the Spotify tutorial on android sdk beta22-noconnect-2.20b to just get the groundwork on an app that I want to to write and I am having trouble getting my application to authenticate. I am sure that I have signed my app and inserted the correct fingerprint into the "My Applications" tab on spotify. My URI is correct. I am also fairly certain that I followed the tutorial 100% properly. Every time I run the program I get this error.
I/OpenGLRenderer: Initialized EGL, version 1.4
D/OpenGLRenderer: Swap behavior 1
D/com.spotify.sdk.android.authentication.LoginActivity: https://accounts.spotify.com/authorize?client_id=7c59d84c8b7f4e35b3c85a7a5289db1b&response_type=token&redirect_uri=spotifymixer%3A%2F%2Fcallback&show_dialog=true&scope=user-read-private%20streaming
D/SpotifyAuthHandler: start
D/com.spotify.sdk.android.authentication.LoginActivity: Error authenticating
D/SpotifyAuthHandler: stop
I don't know how to proceed. Could I have used an incorrect signature for the program?
My MainActivity.java:
package com.example.mammo.spotifyplayer;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.util.Log;
import android.view.View;
import com.spotify.sdk.android.authentication.AuthenticationClient;
import com.spotify.sdk.android.authentication.AuthenticationRequest;
import com.spotify.sdk.android.authentication.AuthenticationResponse;
import com.spotify.sdk.android.player.Config;
import com.spotify.sdk.android.player.ConnectionStateCallback;
import com.spotify.sdk.android.player.Error;
import com.spotify.sdk.android.player.Player;
import com.spotify.sdk.android.player.PlayerEvent;
import com.spotify.sdk.android.player.Spotify;
import com.spotify.sdk.android.player.SpotifyPlayer;
public class MainActivity extends Activity implements
SpotifyPlayer.NotificationCallback, ConnectionStateCallback
{
// TODO: Replace with your client ID
private static final String CLIENT_ID = "7c59d84c8b7f4e35b3c85a7a5289db1b";
// TODO: Replace with your redirect URI
private static final String REDIRECT_URI = "spotifymixer://callback";
private Player mPlayer;
private static final int REQUEST_CODE = 1337;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AuthenticationRequest.Builder builder = new AuthenticationRequest.Builder(CLIENT_ID, AuthenticationResponse.Type.TOKEN, REDIRECT_URI);
builder.setScopes(new String[]{"user-read-private", "streaming"});
AuthenticationRequest request = builder.build();
AuthenticationClient.openLoginActivity(this, REQUEST_CODE, request);
}
public void tryAgain(View view){
AuthenticationRequest.Builder builder = new AuthenticationRequest.Builder(CLIENT_ID, AuthenticationResponse.Type.TOKEN, REDIRECT_URI);
builder.setScopes(new String[]{"user-read-private", "streaming"});
AuthenticationRequest request = builder.build();
AuthenticationClient.openLoginActivity(this, REQUEST_CODE, request);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if(requestCode == REQUEST_CODE){
AuthenticationResponse response = AuthenticationClient.getResponse(resultCode, intent);
if(response.getType() == AuthenticationResponse.Type.TOKEN) {
Config playerConfig = new Config(this, response.getAccessToken(), CLIENT_ID);
Spotify.getPlayer(playerConfig, this, new SpotifyPlayer.InitializationObserver() {
#Override
public void onInitialized(SpotifyPlayer spotifyPlayer) {
mPlayer = spotifyPlayer;
mPlayer.addConnectionStateCallback(MainActivity.this);
mPlayer.addNotificationCallback(MainActivity.this);
}
#Override
public void onError(Throwable throwable) {
Log.e("MainActivity", "Could not initialize player: " + throwable.getMessage());
}
});
}
}
}
#Override
protected void onDestroy() {
Spotify.destroyPlayer(this);
super.onDestroy();
}
#Override
public void onPlaybackEvent(PlayerEvent playerEvent) {
Log.d("MainActivity", "Playback event received: " + playerEvent.name());
switch (playerEvent) {
// Handle event type as necessary
default:
break;
}
}
#Override
public void onPlaybackError(Error error) {
Log.d("MainActivity", "Playback error received: " + error.name());
switch (error) {
// Handle error type as necessary
default:
break;
}
}
#Override
public void onLoggedIn() {
Log.d("MainActivity", "User logged in");
mPlayer.playUri(null, "spotify:track:2TpxZ7JUBn3uw46aR7qd6V", 0, 0);
}
#Override
public void onLoggedOut() {
Log.d("MainActivity", "User logged out");
}
#Override
public void onLoginFailed(int i) {
Log.d("MainActivity", "Login failed");
}
#Override
public void onTemporaryError() {
Log.d("MainActivity", "Temporary error occurred");
}
#Override
public void onConnectionMessage(String message) {
Log.d("MainActivity", "Received connection message: " + message);
}
}
My build.gradle (Module:app):
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "24.0.2"
defaultConfig {
applicationId "com.example.mammo.spotifyplayer"
minSdkVersion 15
targetSdkVersion 24
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
repositories {
mavenCentral()
flatDir {
dirs 'libs'
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
// This library handles authentication and authorization
compile 'com.spotify.sdk:spotify-auth:beta22-noconnect-2.20b#aar'
// This library handles music playback
compile 'com.spotify.sdk:spotify-player:beta22-noconnect-2.20b#aar'
compile 'com.android.support:appcompat-v7:24.2.1'
testCompile 'junit:junit:4.12'
}
And my AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.mammo.spotifyplayer">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity
android:name=".MainActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Needed for LoginActivity to work -->
<activity
android:name="com.spotify.sdk.android.authentication.LoginActivity"
android:theme="#android:style/Theme.Translucent.NoTitleBar"/>
</application>
</manifest>
Each time I run the application, my SecurityException gets thrown and the error from the debugger reads as so:
java.lang.SecurityException: "gps" location provider requires ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission.
This seems like a simple mistake, however, my manifest file is completely correct. Here it is, and here is my MapActivity code as well:
<?xml version="1.0" encoding="utf-8"?>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
<uses-permission android:name="com.dev.cromer.jason.coverme.permission.MAPS_RECEIVE" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data
android:name="com.google.android.gms.version"
android:value="#integer/google_play_services_version" />
<meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value= "#string/google_maps_key" />
<activity
android:name=".MapActivity"
android:label="#string/title_activity_map" >
</activity>
</application>
My Activity:
package com.dev.cromer.jason.coverme;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.support.v4.app.FragmentActivity;
import android.os.Bundle;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
public class MapActivity extends FragmentActivity implements LocationListener {
private GoogleMap mMap; // Might be null if Google Play services APK is not available.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_map);
setUpMapIfNeeded();
}
#Override
protected void onResume() {
super.onResume();
setUpMapIfNeeded();
}
private void setUpMapIfNeeded() {
// Do a null check to confirm that we have not already instantiated the map.
if (mMap == null) {
// Try to obtain the map from the SupportMapFragment.
mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map))
.getMap();
// Check if we were successful in obtaining the map.
if (mMap != null) {
//mMap.setMyLocationEnabled(true);
//mMap.setOnMyLocationChangeListener(this);
setUpMap();
}
}
}
private void setUpMap() {
mMap.addMarker(new MarkerOptions().position(new LatLng(0, 0)).title("Marker"));
mMap.setMyLocationEnabled(true);
LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
try {
Location myLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (myLocation != null) {
Log.d("TAG", "Not null");
}
else {
Log.d("TAG", "NULL");
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
}
}
catch (SecurityException se) {
Log.d("TAG", "SE CAUGHT");
se.printStackTrace();
}
}
#Override
public void onLocationChanged(Location location) {
Log.d("CHANGED", "LOCATION UPDATED");
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
#Override
public void onProviderEnabled(String provider) {
}
#Override
public void onProviderDisabled(String provider) {
}
}
ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION, and WRITE_EXTERNAL_STORAGE are all part of the Android 6.0 runtime permission system. In addition to having them in the manifest as you do, you also have to request them from the user at runtime (using requestPermissions()) and see if you have them (using checkSelfPermission()).
One workaround in the short term is to drop your targetSdkVersion below 23.
But, eventually, you will want to update your app to use the runtime permission system.
For example, this activity works with five permissions. Four are runtime permissions, though it is presently only handling three (I wrote it before WRITE_EXTERNAL_STORAGE was added to the runtime permission roster).
/***
Copyright (c) 2015 CommonsWare, LLC
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.
From _The Busy Coder's Guide to Android Development_
https://commonsware.com/Android
*/
package com.commonsware.android.permmonger;
import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
private static final String[] INITIAL_PERMS={
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.READ_CONTACTS
};
private static final String[] CAMERA_PERMS={
Manifest.permission.CAMERA
};
private static final String[] CONTACTS_PERMS={
Manifest.permission.READ_CONTACTS
};
private static final String[] LOCATION_PERMS={
Manifest.permission.ACCESS_FINE_LOCATION
};
private static final int INITIAL_REQUEST=1337;
private static final int CAMERA_REQUEST=INITIAL_REQUEST+1;
private static final int CONTACTS_REQUEST=INITIAL_REQUEST+2;
private static final int LOCATION_REQUEST=INITIAL_REQUEST+3;
private TextView location;
private TextView camera;
private TextView internet;
private TextView contacts;
private TextView storage;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
location=(TextView)findViewById(R.id.location_value);
camera=(TextView)findViewById(R.id.camera_value);
internet=(TextView)findViewById(R.id.internet_value);
contacts=(TextView)findViewById(R.id.contacts_value);
storage=(TextView)findViewById(R.id.storage_value);
if (!canAccessLocation() || !canAccessContacts()) {
requestPermissions(INITIAL_PERMS, INITIAL_REQUEST);
}
}
#Override
protected void onResume() {
super.onResume();
updateTable();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.actions, menu);
return(super.onCreateOptionsMenu(menu));
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case R.id.camera:
if (canAccessCamera()) {
doCameraThing();
}
else {
requestPermissions(CAMERA_PERMS, CAMERA_REQUEST);
}
return(true);
case R.id.contacts:
if (canAccessContacts()) {
doContactsThing();
}
else {
requestPermissions(CONTACTS_PERMS, CONTACTS_REQUEST);
}
return(true);
case R.id.location:
if (canAccessLocation()) {
doLocationThing();
}
else {
requestPermissions(LOCATION_PERMS, LOCATION_REQUEST);
}
return(true);
}
return(super.onOptionsItemSelected(item));
}
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
updateTable();
switch(requestCode) {
case CAMERA_REQUEST:
if (canAccessCamera()) {
doCameraThing();
}
else {
bzzzt();
}
break;
case CONTACTS_REQUEST:
if (canAccessContacts()) {
doContactsThing();
}
else {
bzzzt();
}
break;
case LOCATION_REQUEST:
if (canAccessLocation()) {
doLocationThing();
}
else {
bzzzt();
}
break;
}
}
private void updateTable() {
location.setText(String.valueOf(canAccessLocation()));
camera.setText(String.valueOf(canAccessCamera()));
internet.setText(String.valueOf(hasPermission(Manifest.permission.INTERNET)));
contacts.setText(String.valueOf(canAccessContacts()));
storage.setText(String.valueOf(hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)));
}
private boolean canAccessLocation() {
return(hasPermission(Manifest.permission.ACCESS_FINE_LOCATION));
}
private boolean canAccessCamera() {
return(hasPermission(Manifest.permission.CAMERA));
}
private boolean canAccessContacts() {
return(hasPermission(Manifest.permission.READ_CONTACTS));
}
private boolean hasPermission(String perm) {
return(PackageManager.PERMISSION_GRANTED==checkSelfPermission(perm));
}
private void bzzzt() {
Toast.makeText(this, R.string.toast_bzzzt, Toast.LENGTH_LONG).show();
}
private void doCameraThing() {
Toast.makeText(this, R.string.toast_camera, Toast.LENGTH_SHORT).show();
}
private void doContactsThing() {
Toast.makeText(this, R.string.toast_contacts, Toast.LENGTH_SHORT).show();
}
private void doLocationThing() {
Toast.makeText(this, R.string.toast_location, Toast.LENGTH_SHORT).show();
}
}
(from this sample project)
For the requestPermissions() function, should the parameters just be "ACCESS_COARSE_LOCATION"? Or should I include the full name "android.permission.ACCESS_COARSE_LOCATION"?
I would use the constants defined on Manifest.permission, as shown above.
Also, what is the request code?
That will be passed back to you as the first parameter to onRequestPermissionsResult(), so you can tell one requestPermissions() call from another.
My simple solution is this
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) ==
PackageManager.PERMISSION_GRANTED) {
googleMap.setMyLocationEnabled(true);
googleMap.getUiSettings().setMyLocationButtonEnabled(true);
} else {
Toast.makeText(this, R.string.error_permission_map, Toast.LENGTH_LONG).show();
}
or you can open permission dialog in else like this
} else {
ActivityCompat.requestPermissions(this, new String[] {
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION },
TAG_CODE_PERMISSION_LOCATION);
}
CAUSE: "Beginning in Android 6.0 (API level 23), users grant permissions to apps while the app is running, not when they install the app." In this case, "ACCESS_FINE_LOCATION" is a "dangerous permission and for that reason, you get this 'java.lang.SecurityException: "gps" location provider requires ACCESS_FINE_LOCATION permission.' error (https://developer.android.com/training/permissions/requesting.html).
SOLUTION: Implementing the code provided at https://developer.android.com/training/permissions/requesting.html under the "Request the permissions you need" and "Handle the permissions request response" headings.