So I've been playing around with Android M style permissions and I have a working demo so long as I do everything from within the MainActivity. I felt that it looked to cumbersome and wanted to split that in to multiple files so it looked a little cleaner and could be more easily maintained.
The problem is by moving the permissions checking into its own file, I am having trouble now getting my permissions checker to go back to MainActivity and launch the demo() method. Where I need to call back to main activities demo method is on line 73 and line 114 of the PermissionsChecker.java
NOTE: I followed this tutorial on Android Permissions
I have found this question as well as these questions here & here. I realize this is dangerously close to a duplicate of the last two, they got me close but I haven't quite been able to get it. If someone can break it down a little more for me, I am still fairly new to Java and Android.
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest package="me.johnweland.androidrtp"
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- // Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<!-- // Features -->
<uses-feature android:name="android.hardware.camera" />
<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">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
MainActivity.java
package me.johnweland.androidrtp;
import android.Manifest;
import android.annotation.TargetApi;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;
import java.util.HashMap;
import java.util.Map;
public class MainActivity extends AppCompatActivity {
final private int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >= 23) {
// Marshmallow+
PermissionChecker permissions = PermissionChecker.getInstance(this);
permissions.permissionsCheck();
} else {
// Pre-Marshmallow
demo();
}
}
#TargetApi(Build.VERSION_CODES.M)
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS:
{
Map<String, Integer> perms = new HashMap<String, Integer>();
// Initial
perms.put(Manifest.permission.RECORD_AUDIO, PackageManager.PERMISSION_GRANTED);
perms.put(Manifest.permission.CAMERA, PackageManager.PERMISSION_GRANTED);
// Fill with results
for (int i = 0; i < permissions.length; i++)
perms.put(permissions[i], grantResults[i]);
// Check for RECORD_AUDIO
if (perms.get(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED
&& perms.get(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
// All Permissions Granted
demo();
}
else {
// Permission Denied
Toast.makeText(this, R.string.permission_denied_message, Toast.LENGTH_SHORT).show();
}
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
protected void demo() {
Toast.makeText(this, "Demo toast", Toast.LENGTH_LONG).show();
}
}
PermissionsChecker.java
package me.johnweland.androidrtp;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Build;
import java.util.ArrayList;
import java.util.List;
/**
* Created by jweland on 12/11/2015.
*/
public class PermissionChecker {
private static final String TAG = PermissionChecker.class.getSimpleName();
final private int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 0;
private MainActivity mainActivity;
private static PermissionChecker instance = null;
private PermissionChecker(MainActivity activity) {
mainActivity = activity;
}
static public PermissionChecker getInstance(MainActivity activity) {
if (instance == null) {
instance = new PermissionChecker(activity);
return instance;
} else {
return instance;
}
}
#TargetApi(Build.VERSION_CODES.M)
protected void permissionsCheck(){
List<String> permissionsNeeded = new ArrayList<String>();
final List<String> permissionsList = new ArrayList<String>();
// Add permission check for any permission that is not NORMAL_PERMISSIONS
if(!addPermission(permissionsList, Manifest.permission.RECORD_AUDIO))
permissionsNeeded.add(mainActivity.getString(R.string.permission_microphone));
if(!addPermission(permissionsList, Manifest.permission.CAMERA))
permissionsNeeded.add(mainActivity.getString(R.string.permission_camera));
if(permissionsList.size() > 0) {
if(permissionsNeeded.size() > 0) {
// Need Rationale
String message = mainActivity.getString(R.string.permission_grant_message) + permissionsNeeded.get(0);
for (int i = 1; i < permissionsNeeded.size(); i++)
message = message + "\n" +permissionsNeeded.get(i);
showMessageOKCancel(message,
new DialogInterface.OnClickListener() {
#TargetApi(Build.VERSION_CODES.M)
#Override
public void onClick(DialogInterface dialog, int which) {
mainActivity.requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
}
});
return;
}
mainActivity.requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
return;
}
mainActivity.demo();
}
private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(mainActivity)
.setMessage(message)
.setPositiveButton(R.string.dialog_ok_button_text, okListener)
.setNegativeButton(R.string.dialog_cancel_button_text, null)
.create()
.show();
}
#TargetApi(Build.VERSION_CODES.M)
private boolean addPermission(List<String> permissionsList, String permission) {
if (mainActivity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
permissionsList.add(permission);
// Check for Rationale Option
if (!mainActivity.shouldShowRequestPermissionRationale(permission))
return false;
}
return true;
}
}
Initially in the last code block on lines 73 and 114 I had tried something to the effect of "MainActivity.demo();" where "demo();" is the method I wish to call.
You can make your class ask for your activity as a parameter, save it on a variable and call it's method whenever you want (assuming that your class and activity run on the same thread).
public class MyActivity extends AppCompatActivity
{
public void myFunction() {/* ... */}
}
public class MyClass
{
MyActivity activity;
//This could be the constructor
public void someFunction(MyActivity gActivity)
{
activity = gActivity;
}
public void anotherFunction()
{
//From activity you can get everything you want like context
//resources and anything else
activity.myFunction();
}
}
Another way you could do it is by creating an interface which saves a function (on your activity) and passes that interface as argument to your class and calls it whenever you want, but that's slightly more complicated, the first method is better in my opinion.
Related
I am working on Nordic NRF52840 periodic advertising and scan the device with Android app Android 10. I ask a question yesterday then I rewrite my code using the tutorial on the internet, basically my app is very easy, press the button then the app will scan and print the ble device on log, but I still can't find my device I don't know what's wrong with my code I add a ton of permission in Manifest.xml but nothing changed I still can't find the device I want. Update: I can find some device,but not my device, I still can't find my device there are a few question I want to ask
I can't find my device, but can find other device.
how to stop scanning, I add a button to stop scanning, but it won't stop.
how to get device name, when I call getName() error pop up and the app closed
getName error
AndroidManifest
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.tryble_scanner">
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission
android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
/>
<uses-permission
android:name="android.permission.BLUETOOTH_ADVERTISE" />
<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_BACKGROUND_LOCATION"/>
<uses-feature android:name="android.hardware.bluetooth"
android:required="true"/>
<uses-feature android:name="android.hardware.bluetooth_le"
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/Theme.TryBle_scanner">
<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>
MainActivity.java
package com.example.tryble_scanner;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class MainActivity extends AppCompatActivity {
private BluetoothAdapter mBluetoothAdapter = null;
private BluetoothLeScanner mBluetoothLeScanner = null;
public static final int REQUEST_BT_PERMISSIONS = 0;
public static final int REQUEST_BT_ENABLE = 1;
private boolean mScanning = false;
private Handler mHandler = null;
private ScanCallback mLeScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, final ScanResult result) {
//super.onScanResult(callbackType, result);
BluetoothDevice btdevice = result.getDevice();
Log.d("BLE", btdevice.getAddress());
}
#Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.d("BLE", "error");
}
};
private ScanCallback mLeScanCallback2=new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
Log.d("BLE","scan stop");
}
#Override
public void onScanFailed(int errorCode) {
Log.d("BLE","stop scan failed");
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btnScan = (Button) findViewById(R.id.btnScan);
BluetoothManager bluetoothManager = getSystemService(BluetoothManager.class);
mBluetoothAdapter = bluetoothManager.getAdapter();
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
this.mHandler = new Handler();
}
public void stop_scan(View view) {
Log.d("Ble","scan stop pressed");
// mBluetoothLeScanner.stopScan(mLeScanCallback2);
mBluetoothAdapter.getBluetoothLeScanner().stopScan(mLeScanCallback2);
}
public void onBtnScan(View view) {
Log.i("Btn","get click");
checkBTPermission();
String[] names=new String[]{"Auden test"};
List<ScanFilter> filters=null;
if(names != null){
filters=new ArrayList<>();
for(String name:names){
ScanFilter filter=new ScanFilter.Builder().setDeviceName(name).build();
filters.add(filter);
}
}
if(mBluetoothLeScanner==null){
Log.i("BLE","could not get scanner");
}else{
mBluetoothLeScanner.startScan(mLeScanCallback); //filters,scanSettings,mLeScanCallback
}
}
private void checkBTPermission(){
if(Build.VERSION.SDK_INT>Build.VERSION_CODES.LOLLIPOP){
int pc=this.checkSelfPermission("Manifest.permission.ACCESS_FINE_LOCATION");
pc+=this.checkSelfPermission("Manifest.permission.ACCESS_COARSE_LOCATION");
if(pc!=0){
this.requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION},1001);
}else {
Log.d("BLE","checkBT permission");
}
}
}
}
If you're done with permissions and the rest, probably you are instantiating the BluetoothAdapter in an incorrect way. Try instantiating it like the following example in your main activity's onCreate method:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnScan = (Button) findViewById(R.id.btnScan);
BluetoothManager bluetoothManager = getSystemService(BluetoothManager.class);
mBluetoothAdapter = bluetoothManager.getAdapter();
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
mHandler = new Handler();
}
Update for test
Well I had to make a new basic project to test it. I'll share te manifest and activity code, also the output. I've tested it on my Android 11 installed mobile. Note that I've removed all scan filters.
manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.karalama">
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<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_BACKGROUND_LOCATION"/>
<uses-feature android:name="android.hardware.bluetooth" android:required="true"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
<application
android:allowBackup="true"
android:dataExtractionRules="#xml/data_extraction_rules"
android:fullBackupContent="#xml/backup_rules"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.Karalama"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="#string/app_name"
android:theme="#style/Theme.Karalama.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
MainActivity
package com.example.karalama;
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.os.Build;
import android.os.Bundle;
import com.google.android.material.snackbar.Snackbar;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import com.example.karalama.databinding.ActivityMainBinding;
import android.view.Menu;
import android.view.MenuItem;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private AppBarConfiguration appBarConfiguration;
private BluetoothAdapter mBluetoothAdapter = null;
private BluetoothLeScanner mBluetoothLeScanner = null;
private boolean mScanning = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
com.example.karalama.databinding.ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
BluetoothManager bluetoothManager = getSystemService(BluetoothManager.class);
mBluetoothAdapter = bluetoothManager.getAdapter();
Log.d("BLE", "onCreate: Bluetoothadapter null"+(mBluetoothAdapter == null));
mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
binding.fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
checkBTPermission();
String[] names=new String[]{"Auden test"};
List<ScanFilter> filters=null;
if(names != null){
filters=new ArrayList<>();
for(String name:names){
ScanFilter filter=new ScanFilter.Builder().setDeviceName(name).build();
filters.add(filter);
}
}
if(mBluetoothLeScanner==null){
Log.i("BLE","could not get scanner");
}else{
mBluetoothLeScanner.startScan(mLeScanCallback);
}
}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public boolean onSupportNavigateUp() {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
return NavigationUI.navigateUp(navController, appBarConfiguration)
|| super.onSupportNavigateUp();
}
private final ScanCallback mLeScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, final ScanResult result) {
//super.onScanResult(callbackType, result);
BluetoothDevice btdevice = result.getDevice();
Log.d("BLE", "Found -> "+btdevice.getAddress());
}
#Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.d("BLE", "error");
}
};
private void checkBTPermission(){
if(Build.VERSION.SDK_INT>Build.VERSION_CODES.LOLLIPOP){
int pc=this.checkSelfPermission("Manifest.permission.ACCESS_FINE_LOCATION");
pc+=this.checkSelfPermission("Manifest.permission.ACCESS_COARSE_LOCATION");
if(pc!=0){
this.requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION},1001);
}else {
Log.d("BLE","checkBT permission");
}
}
}
}
The logcat output
I/RenderThread: type=1400 audit(0.0:1130983): avc: denied { ioctl } for uid=10254 path="/dev/kgsl-3d0" dev="tmpfs" ino=15422 ioctlcmd=945 scontext=u:r:untrusted_app:s0:c254,c256,c512,c768 tcontext=u:object_r:device:s0 tclass=chr_file permissive=1 app=com.example.karalama
I/RenderThread: type=1400 audit(0.0:1130984): avc: denied { read write } for uid=10254 path="/dev/kgsl-3d0" dev="tmpfs" ino=15422 scontext=u:r:untrusted_app:s0:c254,c256,c512,c768 tcontext=u:object_r:device:s0 tclass=chr_file permissive=1 app=com.example.karalama
D/BluetoothAdapter: isLeEnabled(): ON
D/BluetoothLeScanner: onScannerRegistered() - status=0 scannerId=6 mScannerId=0
D/BLE: Found -> C8:FD:19:7F:5A:7D
D/BLE: Found -> 5F:EE:47:E9:F3:A3
D/BLE: Found -> D4:36:39:B5:D3:5D
D/BLE: Found -> D4:36:39:8B:C1:98
D/BLE: Found -> 40:94:41:26:D2:D6
D/BLE: Found -> D4:36:39:8A:A9:AB
D/BLE: Found -> D4:36:39:8B:C1:98
D/BLE: Found -> 5F:EE:47:E9:F3:A3
D/BLE: Found -> 4B:A2:87:44:4C:79
D/BLE: Found -> D4:36:39:8A:A9:AB
D/BLE: Found -> D4:36:39:8B:C1:98
D/BLE: Found -> D4:36:39:B5:D3:5D
D/BLE: Found -> 40:94:41:26:D2:D6
D/BLE: Found -> 4B:A2:87:44:4C:79
D/BLE: Found -> C8:FD:19:7F:5A:7D
D/BLE: Found -> D4:36:39:B5:D3:5D
D/BLE: Found -> D4:36:39:8B:C1:98
D/BLE: Found -> 7A:0F:6B:DD:EC:FB
Update 2 - Get rid of the "getName" error
#Override
public void onScanResult(int callbackType, final ScanResult result) {
//super.onScanResult(callbackType, result);
BluetoothDevice btdevice = result.getDevice();
// The device may not have a name and getName may return null. Fix it in the following way
String name = btdevice.getName() == null ? "No name" : btdevice.getName();
Log.d("BLE", name+" - "+btdevice.getAddress());
}
You do indeed declare the permissions in your manifest file, but keep in mind that some of them are so called runtime permissions. You need to ask the user of your app for the permission at runtime. The Request app permissions site of the Android Developer Guide gives you more information on that topic.
You basically need a permissions callback to handle the users response:
// Register the permissions callback, which handles the user's response to the
// system permissions dialog. Save the return value, an instance of
// ActivityResultLauncher, as an instance variable.
private ActivityResultLauncher<String> requestPermissionLauncher =
registerForActivityResult(new RequestPermission(), isGranted -> {
if (isGranted) {
// Permission is granted. Continue the action or workflow in your
// app.
} else {
// Explain to the user that the feature is unavailable because the
// features requires a permission that the user has denied. At the
// same time, respect the user's decision. Don't link to system
// settings in an effort to convince the user to change their
// decision.
}
});
You also need to actually ask the user for the permission:
if (ContextCompat.checkSelfPermission(
CONTEXT, Manifest.permission.REQUESTED_PERMISSION) ==
PackageManager.PERMISSION_GRANTED) {
// You can use the API that requires the permission.
performAction(...);
} else if (shouldShowRequestPermissionRationale(...)) {
// In an educational UI, explain to the user why your app requires this
// permission for a specific feature to behave as expected. In this UI,
// include a "cancel" or "no thanks" button that allows the user to
// continue using your app without granting the permission.
showInContextUI(...);
} else {
// You can directly ask for the permission.
// The registered ActivityResultCallback gets the result of this request.
requestPermissionLauncher.launch(
Manifest.permission.REQUESTED_PERMISSION);
}
The one permission that's most likely missing is the Location permission. This part of the Developer Guide lists the required permissions for BLE on Android < 11.
Please also use a generic BLE scanner such as nRF Connect to verify that your device actually advertises itself.
you can take help from this link
https://punchthrough.com/android-ble-guide/
if this still not works for use than u can use scan library
https://github.com/NordicSemiconductor/Android-Scanner-Compat-Library
Is there a way to create and save text files outside of the Android/data folder in external storage? I am creating a music app and I added a preset manager so users can save, load, and edit preset files (which worked great before Android 11), I don’t want these files to be automatically removed if the app is uninstalled. That would be like if Photoshop deleted all of your Photoshop documents when Photoshop is uninstalled, that’s terrible! These are files that the user saves and they can be deleted separately if the user wants to.
There has to be a way around this but I haven’t been able to find anything that works. ACTION_OPEN_DOCUMENT_TREE looked very promising, until I saw that Android has removed this option too.
https://developer.android.com/about/versions/11/privacy/storage
You can no longer use the ACTION_OPEN_DOCUMENT_TREE intent action to
request access to the following directories:
The root directory of the internal storage volume.
The root directory of each SD card volume that the device manufacturer considers to be reliable, regardless of whether the card
is emulated or removable. A reliable volume is one that an app can
successfully access most of the time.
The Download directory.
I’ve read Google Play only allows MANAGE_EXTERNAL_STORAGE in apps that need it (like file browsers, or anti-virus, etc) which likely will not work in my case. I don’t want to rely on only targeting older API’s so requestLegacyExternalStorage won’t work either.
Everything I’ve looked into appears to be a dead end. Is there anything else I can do?
Here is a short test program (I’m using LibGDX), which at the moment can only save to the root location:
Android/data/com.mygdx.filetest/files/
[core] FileTest.java
package com.mygdx.filetest;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Files;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.ScreenUtils;
public class FileTest implements ApplicationListener {
private NativePermissions permissions;
private Files.FileType fileType;
private String directory = "TestDir/";
private String name = "text.txt";
public FileTest(final NativePermissions permissions){
this.permissions = permissions;
}
#Override
public void create(){
fileType = getFileType();
if (permissions != null){
permissions.checkExternalStoragePermission();
} else {
permissionGranted();
}
}
private Files.FileType getFileType(){
switch(Gdx.app.getType()) {
case Android:
return Files.FileType.External;
default:
return Files.FileType.Local;
}
}
#Override public void render(){ ScreenUtils.clear(0.4f, 0.4f, 0.4f, 1); }
#Override public void resize(int width, int height) {}
#Override public void pause(){}
#Override public void resume(){}
#Override public void dispose (){}
public void permissionGranted() {
FileHandle fileHandle = Gdx.files.getFileHandle(directory+name, fileType);
if (fileHandle!=null) fileHandle.writeString("test", false);
}
}
[android] AndroidLauncher.java
package com.mygdx.filetest;
import android.Manifest;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;
public class AndroidLauncher extends AndroidApplication {
private final FileTest application;
private final int STORAGE_PERMISSION_CODE = 1;
private boolean dialogBoxShowing = false;
public AndroidLauncher(){
final AndroidPermissions permissions = new AndroidPermissions(this);
application = new FileTest(permissions);
}
#Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
initialize(application, config);
}
#Override
public void onRequestPermissionsResult(final int requestCode, final String permissions[], final int[] grantResults) {
dialogBoxShowing = false;
if (requestCode == STORAGE_PERMISSION_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "Permission GRANTED", Toast.LENGTH_SHORT).show();
permissionGranted();
} else {
Toast.makeText(this, "Permission DENIED", Toast.LENGTH_SHORT).show();
}
}
}
public void promptExternalStoragePermission() {
if (dialogBoxShowing) return;
dialogBoxShowing = true;
this.runOnUiThread(new Runnable() {
#Override
public void run() {
final AlertDialog.Builder builder = new AlertDialog.Builder(AndroidLauncher.this);
builder.setMessage("To save user presets and custom settings, allow access to your phone’s storage.");
builder.setCancelable(false);
// reverse these buttons to put "NO" on left and "YES" on right
builder.setPositiveButton("NOT NOW", new DialogInterface.OnClickListener(){
#Override
public void onClick(DialogInterface dialog, int which) {
dialogBoxShowing = false;
dialog.dismiss();
}
});
builder.setNegativeButton("CONTINUE", new DialogInterface.OnClickListener(){
#Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(AndroidLauncher.this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, STORAGE_PERMISSION_CODE);
}
});
builder.create().show();
}
});
}
public void permissionGranted(){ application.permissionGranted(); }
}
[core] NativePermissions.java
package com.mygdx.filetest;
public interface NativePermissions {
public void checkExternalStoragePermission();
}
[android] AndroidPermissions.java
package com.mygdx.filetest;
import android.Manifest;
import android.content.pm.PackageManager;
import androidx.core.content.ContextCompat;
public class AndroidPermissions implements NativePermissions {
private final AndroidLauncher context;
public AndroidPermissions(final AndroidLauncher context){
this.context = context;
}
#Override
public void checkExternalStoragePermission() {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED){
context.permissionGranted();
} else {
context.promptExternalStoragePermission();
}
}
}
[android] AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mygdx.filetest">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:isGame="true"
android:appCategory="game"
android:label="#string/app_name"
android:theme="#style/GdxTheme" >
<activity
android:name="com.mygdx.filetest.AndroidLauncher"
android:label="#string/app_name"
android:screenOrientation="fullUser"
android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|screenLayout">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
A lott of fuss.
You can in the classic way save your files to the public Documents directory.
Or use SAF with ACTION_OPEN_DOCUMENT_TREE for that directory.
Both dont need 'all files access'.
I understand this questions has been asked many times, for each question that has already been asked, the implementation is different from mine. The method I used create and call the intentService for a geocoder, was used from the android dev tutorial site.
https://developer.android.com/training/location/display-address.html.
I have gone over the the tutorial 3 times over to make sure I wasn't missing anything, but it is clear that I am.
Here is my manifest file, I have added the location and internet permissions.
<?xml version="1.0" encoding="utf-8"?>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<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=".selectRouteAndTransportMethod">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<service
android:name=".FetchAddressIntentService"
android:exported="false"/>
</activity>
<!--
The API key for Google Maps-based APIs is defined as a string resource.
(See the file "res/values/google_maps_api.xml").
Note that the API key is linked to the encryption key used to sign the APK.
You need a different API key for each encryption key, including the release key that is used to
sign the APK for publishing.
You can define the keys for the debug and release targets in src/debug/ and src/release/.
-->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="#string/google_maps_key" />
<activity
android:name="user.com.commuterapp.MapsActivity"
android:label="#string/title_activity_maps"></activity><!-- ATTENTION: This was auto-generated to add Google Play services to your project for
App Indexing. See https://g.co/AppIndexing/AndroidStudio for more information. -->
<meta-data
android:name="com.google.android.gms.version"
android:value="#integer/google_play_services_version" />
</application>
Here is my main activity: All I am doing here is using a buttonClick to create the intent and to start the intent.
package user.com.commuterapp;
import android.app.PendingIntent;
import android.content.Intent;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationServices;
import user.com.commuterapp.R;
import static java.lang.Boolean.TRUE;
public class selectRouteAndTransportMethod extends AppCompatActivity {
Intent mIntent;
PendingIntent mPendingIntent;
myLocation mCurrentLocation;
private AddressResultReceiver mResultReceiver;
public static final String TAG = selectRouteAndTransportMethod.class.getSimpleName();
/**
* ATTENTION: This was auto-generated to implement the App Indexing API.
* See https://g.co/AppIndexing/AndroidStudio for more information.
*/
private GoogleApiClient client;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_select_route_and_transport_method);
mIntent = new Intent(this, selectRouteAndTransportMethod.class);
mPendingIntent = PendingIntent.getActivity(this, 0, mIntent, 0);
mCurrentLocation = new myLocation(this, this, mPendingIntent);
// ATTENTION: This was auto-generated to implement the App Indexing API.
// See https://g.co/AppIndexing/AndroidStudio for more information.
}
#Override
protected void onStart() {
super.onStart();
Log.d(TAG, "On Start");
mCurrentLocation.connect();
boolean connecting = mCurrentLocation.mGoogleApiClient.isConnecting();
boolean registered = mCurrentLocation.mGoogleApiClient.isConnectionCallbacksRegistered(mCurrentLocation);
//ConnectionResult connectionResult = mCurrentLocation.mGoogleApiClient.getConnectionResult(LocationServices.API);
if (connecting == TRUE) {
Log.d(TAG, "Connecting");
}
if (registered == TRUE) {
Log.d(TAG, "registered");
}
}
#Override
protected void onResume() {
super.onResume();
mCurrentLocation.connect();
}
#Override
protected void onPause() {
super.onPause();
// mCurrentLocation.disconnect();
}
protected void buttonOriginClick(View view)
{
Log.d(TAG,"retrieving address");
retrieveAddress();
}
protected void retrieveAddress() {
Intent geoCoderIntent = new Intent(this, FetchAddressIntentService.class);
geoCoderIntent.putExtra(FetchAddressIntentService.Constants.RECIEVER, mResultReceiver);
geoCoderIntent.putExtra(FetchAddressIntentService.Constants.LOCATION_DATA_EXTRA, mCurrentLocation.mCurrentLocation);
startService(geoCoderIntent);
}
}
And lastly here is the intentService class. This was straight out of the android dev tutorial site, nothing original here.
package user.com.commuterapp;
import android.app.IntentService;
import android.content.Intent;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.os.Bundle;
import android.support.v4.os.ResultReceiver;
import android.text.TextUtils;
import android.util.Log;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import static android.content.ContentValues.TAG;
/**
* Created by User on 1/21/2017.
*/
public class FetchAddressIntentService extends IntentService {
public final class Constants{
public static final int SUCCESS_RESULT = 0;
public static final int FAILURE_RESULT = 1;
public static final String PACKAGE_NAME =
"user.com.commuterapp";
public static final String RECIEVER = PACKAGE_NAME +
".RECIEVER";
public static final String RESULT_DATA_KEY = PACKAGE_NAME + ".RESULT_DATA_KEY";
public static final String LOCATION_DATA_EXTRA = PACKAGE_NAME +
".LOCATION_DATA_EXTRA";
}
protected ResultReceiver mReceiver;
public FetchAddressIntentService()
{
super("FetchAddressIntentService");
}
private void deliverResultToReceiver(int resultCode, String message){
Bundle bundle = new Bundle();
bundle.putString(Constants.RESULT_DATA_KEY, message);
mReceiver.send(resultCode,bundle);
}
#Override
protected void onHandleIntent (Intent intent) {
Log.d(TAG, "GeoCoder Service Started");
Geocoder geocoder = new Geocoder(this, Locale.getDefault());
String errorMessage = "";
Location location = intent.getParcelableExtra(Constants.LOCATION_DATA_EXTRA);
List<Address> addresses = null;
try {
addresses = geocoder.getFromLocation(
location.getLongitude(),
location.getLatitude(),
1);
}catch (IOException ioException) {
//catch network or other I/O Problems
errorMessage = getString(R.string.service_not_available);
Log.e(TAG, errorMessage);
} catch (IllegalArgumentException illegalArgumentException){
//catch invalid latitude or longitude values
errorMessage = getString(R.string.invalid_lat_long_used);
Log.e(TAG, errorMessage + "." + "Latitude =" + location.getLatitude()
+ ", Longitude = " + location.getLongitude(),
illegalArgumentException);
}
//handle cases where no addresses was foudn
if (addresses == null || addresses.size() == 0)
{
if(errorMessage.isEmpty()){
errorMessage = getString(R.string.no_address_found);
Log.e(TAG,errorMessage);
}
deliverResultToReceiver(Constants.FAILURE_RESULT, errorMessage);
}else
{
Address address = addresses.get(0);
ArrayList<String> addressFragments = new ArrayList<String>();
//Fetch the address lines using getAddressLine
//join them and send them to the thread
for(int i = 0; i < address.getMaxAddressLineIndex(); i++){
addressFragments.add(address.getAddressLine(i));
}
Log.i(TAG, getString(R.string.address_found));
deliverResultToReceiver(Constants.SUCCESS_RESULT,
TextUtils.join(System.getProperty("line.seperator"), addressFragments));
}
}
}
EDIT: I have tried rewriting the geocoder following the instructions from the android dev site again, but no success.
EDIT2: I noticed in the manifest I declared the service, within the application declaration, instead of outside, I made the change but to no avail, any insights will be appreciated
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.
I am currently working with ActivityRecognitionClient , but unfortunately that Google has announced that the class had been deprecated and to use GoogleApiClient instead.
Not sure if I am doing it wrong or not, I am getting confused with the new API file. I have imported the Google Play Libraries, setup the API v2 key. I followed an online source on coding up the ActivityRecognitionClient version.
Below are the codes of the different files, whenever I switch tab to the actRecog it crashes and points the error to this line with a null pointer exception.
mActivityRecognitionPendingIntent = PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Full source code below : (API v2 keys are intentionally hidden for privacy purposes.)
MainActivity.java
package com.example.healthgps;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.app.Activity;
import android.app.TabActivity;
import android.content.Context;
import android.content.Intent;
import android.widget.TabHost;
import android.widget.TabHost.TabSpec;
public class MainActivity extends TabActivity {
TabHost mTabHost;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTabHost = getTabHost();
TabSpec firstSpec = mTabHost.newTabSpec("Stats");
firstSpec.setIndicator("Stats");
Intent firstIntent = new Intent(this, FirstActivity.class);
firstSpec.setContent(firstIntent);
TabSpec thirdSpec = mTabHost.newTabSpec("ActRecog");
thirdSpec.setIndicator("ActRecog");
Intent thirdIntent = new Intent(this, activityrecignition.class);
thirdSpec.setContent(thirdIntent);
mTabHost.addTab(firstSpec);
mTabHost.addTab(thirdSpec);
}
}
Manifest XML File :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.healthgps"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="11"
android:targetSdkVersion="17" />
<permission
android:name="com.example.healthgps.permission.MAPS_RECEIVE"
android:protectionLevel="signature" />
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION"/>
<uses-permission android:name="com.example.healthgps.permission.MAPS_RECEIVE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" />
<activity
android:name="com.example.healthgps.MainActivity"
android:label="Health Kit"
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".FirstActivity"
android:label="Health Kit" >
</activity>
<activity
android:name=".activityrecignition"
android:label="Health Kit" >
</activity>
<service
android:name="com.example.healthgps.ActivityRecognitionIntentService"
android:label="#string/app_name"
android:exported="false">
</service>
<meta-data
android:name="com.google.android.gms.version"
android:value="#integer/google_play_services_version" />
</application>
</manifest>
activityrecignition.java (purposely typo for the name)
package com.example.healthgps;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.IntentService;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks;
import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.ActivityRecognition;
import com.google.android.gms.location.ActivityRecognitionResult;
import com.google.android.gms.location.DetectedActivity;
public class activityrecignition extends FragmentActivity implements ConnectionCallbacks,OnConnectionFailedListener {
public static final int MILLISECONDS_PER_SECOND = 1000;
public static final int DETECTION_INTERVAL_SECONDS = 20;
public static final int DETECTION_INTERVAL_MILLISECONDS =
MILLISECONDS_PER_SECOND * DETECTION_INTERVAL_SECONDS;
private final static int
CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000;
private PendingIntent mActivityRecognitionPendingIntent;
// Store the current activity recognition client
private GoogleApiClient mGoogleApiClient;
private Context mContext;
private Intent intent;
TextView tv;
ActivityRecognitionIntentService ar;
private boolean mInProgress;
public enum REQUEST_TYPE {START, STOP}
private REQUEST_TYPE mRequestType;
Intent i;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.third);
mActivityRecognitionPendingIntent = PendingIntent.getService(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
tv=(TextView) findViewById(R.id.activityname);
mInProgress=false;
ar.onHandleIntent(i);
}
#SuppressLint("NewApi") public static class ErrorDialogFragment extends DialogFragment {
private Dialog mDialog;
#SuppressLint("NewApi") public ErrorDialogFragment() {
super();
mDialog = null;
}
public void setDialog(Dialog dialog) {
mDialog = dialog;
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return mDialog;
}
public void show(FragmentManager supportFragmentManager, String tag) {
}
}
#Override
protected void onActivityResult(
int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case CONNECTION_FAILURE_RESOLUTION_REQUEST :
switch (resultCode) {
case Activity.RESULT_OK :
break;
}
}
}
private boolean servicesConnected() {
int resultCode =
GooglePlayServicesUtil.
isGooglePlayServicesAvailable(this);
if (ConnectionResult.SUCCESS == resultCode) {
Log.d("Activity Recognition", "Google Play services is available.");
return true;
} else {
Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(
resultCode,
this,
CONNECTION_FAILURE_RESOLUTION_REQUEST);
if (errorDialog != null) {
// Create a new DialogFragment for the error dialog
ErrorDialogFragment errorFragment = new ErrorDialogFragment();
// Set the dialog in the DialogFragment
errorFragment.setDialog(errorDialog);
// Show the error dialog in the DialogFragment
errorFragment.show(
getSupportFragmentManager(),
"Activity Recognition");
}
return false;
}
}
public class ActivityRecognitionIntentService extends IntentService {
public ActivityRecognitionIntentService(String name) {
super(name);
// TODO Auto-generated constructor stub
}
private String getNameFromType(int activityType) {
switch(activityType) {
case DetectedActivity.IN_VEHICLE:
return "in_vehicle";
case DetectedActivity.ON_BICYCLE:
return "on_bicycle";
case DetectedActivity.ON_FOOT:
return "on_foot";
case DetectedActivity.STILL:
return "still";
case DetectedActivity.UNKNOWN:
return "unknown";
case DetectedActivity.TILTING:
return "tilting";
}
return "unknown";
}
#Override
protected void onHandleIntent(Intent intent) {
// If the incoming intent contains an update
if (ActivityRecognitionResult.hasResult(intent)) {
// Get the update
ActivityRecognitionResult result =
ActivityRecognitionResult.extractResult(intent);
// Get the most probable activity
DetectedActivity mostProbableActivity =
result.getMostProbableActivity();
/*
* Get the probability that this activity is the
* the user's actual activity
*/
int confidence = mostProbableActivity.getConfidence();
/*
* Get an integer describing the type of activity
*/
int activityType = mostProbableActivity.getType();
String activityName = getNameFromType(activityType);
tv.setText(activityName);
/*
* At this point, you have retrieved all the information
* for the current update. You can display this
* information to the user in a notification, or
* send it to an Activity or Service in a broadcast
* Intent.
*/
} else {
/*
* This implementation ignores intents that don't contain
* an activity update. If you wish, you can report them as
* errors.
*/
tv.setText("There are no updates!!!");
}
}
}
public void onClick(View v){
if(v.getId()==R.id.Start){
startUpdates();
}
if(v.getId()==R.id.Stop){
stopUpdates();
}
}
public void startUpdates() {
// Check for Google Play services
mRequestType = REQUEST_TYPE.START;
if (!servicesConnected()) {
return;
}
// If a request is not already underway
if (!mInProgress) {
// Indicate that a request is in progress
mInProgress = true;
// Request a connection to Location Services
mGoogleApiClient.connect();
//
} else {
/*
* A request is already underway. You can handle
* this situation by disconnecting the client,
* re-setting the flag, and then re-trying the
* request.
*/
mInProgress = true;
mGoogleApiClient.disconnect();
ActivityRecognition.ActivityRecognitionApi.requestActivityUpdates(mGoogleApiClient, DETECTION_INTERVAL_MILLISECONDS, mActivityRecognitionPendingIntent);
}
}
#Override
public void onConnected(Bundle dataBundle) {
// TODO Auto-generated method stub
ActivityRecognition.ActivityRecognitionApi.requestActivityUpdates(mGoogleApiClient, DETECTION_INTERVAL_MILLISECONDS, mActivityRecognitionPendingIntent);
/*
* Since the preceding call is synchronous, turn off the
* in progress flag and disconnect the client
*/
mInProgress = false;
mGoogleApiClient.disconnect();
switch (mRequestType) {
case START :
/*
* Request activity recognition updates using the
* preset detection interval and PendingIntent.
* This call is synchronous.
*/
ActivityRecognition.ActivityRecognitionApi.requestActivityUpdates(mGoogleApiClient, DETECTION_INTERVAL_MILLISECONDS, mActivityRecognitionPendingIntent);
break;
case STOP :
ActivityRecognition.ActivityRecognitionApi.removeActivityUpdates(mGoogleApiClient, mActivityRecognitionPendingIntent);
/*
* An enum was added to the definition of REQUEST_TYPE,
* but it doesn't match a known case. Throw an exception.
*/
default :
try {
throw new Exception("Unknown request type in onConnected().");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
}
}
#Override
public void onDisconnected() {
// TODO Auto-generated method stub
mInProgress = false;
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
// Turn off the request flag
mInProgress = false;
/*
* If the error has a resolution, start a Google Play services
* activity to resolve it.
*/
if (connectionResult.hasResolution()) {
try {
connectionResult.startResolutionForResult(
this,
CONNECTION_FAILURE_RESOLUTION_REQUEST);
} catch (SendIntentException e) {
// Log the error
e.printStackTrace();
}
// If no resolution is available, display an error dialog
} else {
// Get the error code
int errorCode = connectionResult.getErrorCode();
// Get the error dialog from Google Play services
Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(
errorCode,
this,
CONNECTION_FAILURE_RESOLUTION_REQUEST);
// If Google Play services can provide an error dialog
if (errorDialog != null) {
// Create a new DialogFragment for the error dialog
ErrorDialogFragment errorFragment =
new ErrorDialogFragment();
// Set the dialog in the DialogFragment
errorFragment.setDialog(errorDialog);
// Show the error dialog in the DialogFragment
errorFragment.show(
getSupportFragmentManager(),
"Activity Recognition");
}
}
}
public void stopUpdates() {
// Set the request type to STOP
mRequestType = REQUEST_TYPE.STOP;
/*
* Test for Google Play services after setting the request type.
* If Google Play services isn't present, the request can be
* restarted.
*/
if (!servicesConnected()) {
return;
}
// If a request is not already underway
if (!mInProgress) {
// Indicate that a request is in progress
mInProgress = true;
// Request a connection to Location Services
mGoogleApiClient.connect();
//
} else {
/*
* A request is already underway. You can handle
* this situation by disconnecting the client,
* re-setting the flag, and then re-trying the
* request.
*/
}
}
}
In the old code, it contains a part where the ActivityRecognitionClient requires an instantiation, but GoogleApiClient doesn't have.
Is there anyone who manage to switch over to the new API already ? I need some guide to get it there.
Thanks.
you must use GoogleApiClient.Builder in the following way
GoogleApiClient.Builder builder = new GoogleApiClient.Builder(<context>)
.addApi(<some api, i.e LocationServices.API>)
.addConnectionCallbacks(new ConnectionCallbacks() {
#Override
public void onConnectionSuspended(int arg) {}
#Override
public void onConnected(Bundle arg0) {
Intent intent = new Intent(getApplicationContext(), ActivityRecognitionService.class); // your custom ARS class
mPendingIntent = PendingIntent.getService(getApplicationContext(), 0, intent,PendingIntent.FLAG_UPDATE_CURRENT);
ActivityRecognition.ActivityRecognitionApi
.requestActivityUpdates(mGoogleApiClient, ACTIVITY_RECOGNITION_INTERVAL, mPendingIntent);}
}
.addOnConnectionFailedListener(new OnConnectionFailedListener() {
#Override
public void onConnectionFailed(ConnectionResult arg0) {
}
});
mGoogleApiClient = builder.build();
mGoogleApiClient.connect();