No static method canDrawOverlays - android

I've noticed someone who is using my app reported a crash which logged by the Google Developer Console:
java.lang.NoSuchMethodError: No static method canDrawOverlays(Landroid/content/Context;)Z in class Landroid/provider/Settings; or its super classes (declaration of 'android.provider.Settings' appears in /system/framework/framework.jar)
at com.pack.MainActivity.checkDrawOverlayPermission(MainActivity.java:311)
at com.pack.MainActivity.onCreate(MainActivity.java:127)
at android.app.Activity.performCreate(Activity.java:6033)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2288)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2397)
at android.app.ActivityThread.access$800(ActivityThread.java:151)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1310)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5268)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:902)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:697)
The canDrawOverlays is an API 23+ method and I Use it like that:
/** code to post/handler request for permission */
public final static int REQUEST_CODE = 100; /*(see edit II)*/
#SuppressLint("NewApi")
public void checkDrawOverlayPermission() {
/** check if we already have permission to draw over other apps */
if (!Settings.canDrawOverlays(this)) {
/** if not construct intent to request permission */
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
/** request permission via start activity for result */
startActivityForResult(intent, REQUEST_CODE);
}
}
And I have this in my MainActivity:
checkDrawOverlayPermission();
The device which crashed using Android 5.1
HOw I can make sure my app will work on ANdroid 5.1? (API 22 and below) who don't have this method which available from API 23 and up?

Check the current API of the device which runs your code. If it >= 23, you can use the code
if(Build.VERSION.SDK_INT >= 23) {
// if (!Settings.canDrawOverlays(this)) {
//
} else {
// another similar method that supports device have API < 23
}

Use this
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
/** request permission via start activity for result */
startActivityForResult(intent, REQUEST_CODE);
}
} else {
}

For all methods that appeared in newer SDK versions you should use the following pattern:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// code for Marshmallow (SDK 23) and newer versions
} else {
// fallback for older versions
}

Related

Return to app immediately when user has granted the manage external storage permission on Android - Non-deprecated way

I am making an android app that needs the MANAGE_EXTERNAL_STORAGE permission (and the read/write permission before android 11). The app requests the permission the first time it starts (using the code of the accepted answer from here).
However, when the user provides the permission they have to manually press the back button to return to the app so it can continue its flow.
I have a file manager app on my phone that returns to the app automatically once the permission is granted. So I am looking for a way to implement this in my app as well, preferably using the new method google suggest instead of the now deprecated startActivityForResult
The app I mention is available on playstore here
What I tried?
Replaced the startActivityForResult using ActivityResultLauncher, which is the way google recommends, because startActivityForResult is now deprecated, but it didn't really change the app's behavior/solve the problem. In fact, it seems to be receiving a rejected result whether the user accepts it or not.
After searching more thoroughly I found this post and I used part 3) of this answer, which seems to be doing the trick. I also tried replacing the startActivityForResult with the ActivityResultLauncher that Google recommends and it works fine. So the final code is:
Paste this code on the start of MainActivity(or in the activity you want), just after the properties' declarations:
Handler handler = new Handler();
Runnable checkSettingOn = new Runnable() {
#Override
public void run() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
return;
} else {
if (Environment.isExternalStorageManager()) {
//You have the permission, re-launch MainActivity
Intent i = new Intent(MainActivity.this, MainActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);
return;
}
handler.postDelayed(this, 200);
}
}
};
After this, create an ActivityResultLauncher, which will handle the user's denial part:
ActivityResultLauncher<Intent> storageLauncher =registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() {
#Override
public void onActivityResult(ActivityResult result) {
if (SDK_INT >= Build.VERSION_CODES.R)
if (!Environment.isExternalStorageManager())
//DO STH IF USER DENIES
}
});
When asking for the permission you then start the handler:
try {
Intent storageIntentTry = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
storageIntentTry.addCategory("android.intent.category.DEFAULT");
storageIntentTry.setData(Uri.parse(String.format("package:%s", getApplicationContext().getPackageName())));
storageLauncher.launch(storageIntentTry);
handler.postDelayed(checkSettingOn, 1000);
} catch (Exception e) {
Intent storageIntentCatch = new Intent();
storageIntentCatch.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
storageLauncher.launch(storageIntentCatch);
handler.postDelayed(checkSettingOn, 1000);
}
(Ignore names of intents - can be anything that makes sense)

cannot start activity background in android 10 [ android Q ]

I use android 10 [android Q, galaxy 10],
I use android studio 3.3,
using AVD, and made a api 29 [android 10] virtual phone.
at the virtual machine,
I execute my app , after that, I launch other app like calendar, calculator.
so my app activity get into background mode.
when I receive a message at BroadcastReceiver.
I call startActivity.
here, code -->
public class myReceiver extends BroadcastReceiver {}
public void onReceive(Context context, Intent intent)
{
Intent intentRun = new Intent(context, LoginSuccess.class);
intentRun.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK);
context.startActivity(intentRun);
}
but LoginSuccess activity do not shows up.
[when my app is in background mode]
using same code, LoginSuccess activity show up very well
when my app is in foreground mode.
call stack capture image
above image shows call stack
right before I call startActivity in broadcast receiver.
I have read guide line for android 10 background activity issue.
[developer.android.com/~~ some location]
at the guide line,
I came to know that
if the activity exists in call stack, it can be started
even in background mode.
above code,
it try to start activity that exists in recent call stack.
why startActivity call fail in background mode ?
[maybe not fail , but anyway not activated into foreground]
With Android Q, it is impossible to start an activity from the background automatically if your app does not include those exceptions listed in the link below.
https://developer.android.com/guide/components/activities/background-starts
Possible Solutions:
1- You can choose just show a service notification, and start pending intent with a click
2- You can use full-screen intents to show your intent immediately as shown in the other answer and suggested by Google.
For full-screen intent solution, as described in the official document
The system UI may choose to display a heads-up notification, instead
of launching this intent, while the user is using the device.
3- To start the activity automatically in the background, The most possible solution in my view is adding "SYSTEM_ALERT_WINDOW" to the manifest file. And ask for user permission once when the app opened the first time. (The user can give this permission manually - (Settings-Apps-Your App-Advanced- Draw over other apps))
Example code to request permission :
In Manifest:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
Somewhere in app:
public static int ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE= 2323;
//if the user already granted the permission or the API is below Android 10 no need to ask for permission
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
!Settings.canDrawOverlays(getContext()))
{RequestPermission()}
private void RequestPermission() {
// Check if Android M or higher
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Show alert dialog to the user saying a separate permission is needed
// Launch the settings activity if the user prefers
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getActivity().getPackageName()));
startActivityForResult(intent, ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE);
}
}
#Override
public void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(getContext())) {
PermissionDenied();
}
else
{
// Permission Granted-System will work
}
}
}
}
I'm open activity using the below logic. as google, blog says if you want to open activity in background service for use notification on android 10 or higher.
In Manifest:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
Example:
private void startActivity() {
Uri sound = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.siren);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
AudioAttributes attributes = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_ALARM)
.build();
String CHANNEL_ID = BuildConfig.APPLICATION_ID.concat("_notification_id");
String CHANNEL_NAME = BuildConfig.APPLICATION_ID.concat("_notification_name");
assert notificationManager != null;
NotificationChannel mChannel = notificationManager.getNotificationChannel(CHANNEL_ID);
if (mChannel == null) {
mChannel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
mChannel.setSound(sound, attributes);
notificationManager.createNotificationChannel(mChannel);
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID);
builder.setSmallIcon(R.drawable.logo)
.setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.login))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_CALL)
.setFullScreenIntent(openScreen(Constants.NOTIFICATION_ID), true)
.setAutoCancel(true)
.setOngoing(true);
Notification notification = builder.build();
notificationManager.notify(Constants.NOTIFICATION_ID, notification);
} else {
startActivity(new Intent(BackgroundService.this, LoginActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
}
private PendingIntent openScreen(int notificationId) {
Intent fullScreenIntent = new Intent(this, LoginActivity.class);
fullScreenIntent.putExtra(Constants.NOTIFICATION_IDS, notificationId);
return PendingIntent.getActivity(this, 0, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT);
}
If you have root permissions you can simply use the am command for this in the shell:
public static final void switchAcitivty (final Context context) throws IOException {
final Runtime runtime = Runtime.getRuntime();
final String intentCommand = "su -c am start -n yourpackage/.MainActivity -a android.intent.action.VIEW";
Log.i("TAG", intentCommand);
runtime.exec(intentCommand);
}
It gets blocked without root permission (silently, which is annoying).
Very strange but launching activity from foreground service worked in release build. Was not working in debug build (when debugging via Android Studio).

Location updates are not working in Device Admin / Managed Work Profile Mode

When location updates are requested with LocationManager as well as FusedLocationProviderClient, I'm unable to receive location updates in my app. So I have tried below code to allow the location updates in my app, but it is not working.
manager = (DevicePolicyManager)
getSystemService(Context.DEVICE_POLICY_SERVICE);
if (manager.isProfileOwnerApp(getApplicationContext().getPackageName())) {
ComponentName componentName = new ComponentName(this, CommCareDeviceAdminReceiver.class);
manager.setSecureSetting(componentName, Settings.Secure.LOCATION_MODE, String.valueOf(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY));
}
I received following error in my logcat -
06-15 20:00:33.885 18226-18226/? W/System.err: java.lang.SecurityException: Permission denial: Profile owners cannot update location_mode
at android.os.Parcel.readException(Parcel.java:1684)
at android.os.Parcel.readException(Parcel.java:1637)
at android.app.admin.IDevicePolicyManager$Stub$Proxy.setSecureSetting(IDevicePolicyManager.java:6238)
at android.app.admin.DevicePolicyManager.setSecureSetting(DevicePolicyManager.java:5533)
at org.commcare.devicepolicycontroller.CommCareDeviceAdminReceiver.enableProfile(CommCareDeviceAdminReceiver.java:69)
at org.commcare.devicepolicycontroller.CommCareDeviceAdminReceiver.onProfileProvisioningComplete(CommCareDeviceAdminReceiver.java:51)
at android.app.admin.DeviceAdminReceiver.onReceive(DeviceAdminReceiver.java:665)
at android.app.ActivityThread.handleReceiver(ActivityThread.java:3061)
at android.app.ActivityThread.-wrap18(ActivityThread.java)
06-15 20:00:33.886 18226-18226/? W/System.err: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1574)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6165)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:888)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:778)
So far, I have found this link, which is not much helpful to me for now.
Can anyone please help me guide in this issue?
After almost 5 days of research, I have found the solution to this issue.
This link helped me a lot to find a solution to this problem.
To request enabling the Work/Managed Profile for Admin access, we need to do it as below:
/**
* Initiates the managed profile provisioning. If we already have a managed profile set up on
* this device, we will get an error dialog in the following provisioning phase.
*/
private void provisionManagedProfile() {
Activity activity = getActivity();
if (null == activity) {
return;
}
PackageManager packageManager = activity.getPackageManager();
if (!packageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)) {
String message = getString(R.string.managed_users_unsupported);
HyperLog.e(Constants.TAG, message);
((TextView)activity.findViewById(R.id.setup_text)).setText(message);
return;
}
Intent intent = new Intent(ACTION_PROVISION_MANAGED_PROFILE);
if (Build.VERSION.SDK_INT >= 24) {
intent.putExtra(DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
CommCareDeviceAdminReceiver.getComponentName(activity));
}
else {
intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME,
activity.getApplicationContext().getPackageName());
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, CommCareDeviceAdminReceiver.getComponentName(activity));
}
if (intent.resolveActivity(packageManager) != null) {
startActivityForResult(intent, REQUEST_PROVISION_MANAGED_PROFILE);
activity.finish();
} else {
Toast.makeText(activity, "Device provisioning is not enabled. Stopping.",
Toast.LENGTH_SHORT).show();
}
}
And once it is enabled, you need to add below code in onEnabled method of DeviceAdminReceiver. We will use setPermissionGrantState method from DevicePolicyManager. Additionally, we can also prevent these managed profile user from going into safe mode by using addUseRestriction Method.
/**
* Handles events related to managed profile.
*/
public class MyDeviceAdminReceiver extends DeviceAdminReceiver {
/**
* Called after the administrator is first enabled
*/
#Override
public void onEnabled(Context context, Intent intent) {
super.onEnabled(context, intent);
DevicePolicyManager manager =
(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
if (manager != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//ADDING DYNAMIC LOCATIONS PERMISSIONS IS VERY IMPORTANT IS MANAGED PROFILE
manager.setPermissionGrantState(getComponentName(context)
, "com.example.mypackagename"
, Manifest.permission.ACCESS_FINE_LOCATION
, DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
manager.setPermissionGrantState(getComponentName(context)
, "com.example.mypackagename"
, Manifest.permission.ACCESS_COARSE_LOCATION
, DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
//------------------------------------------
// To disable safe boot
manager.addUserRestriction(getComponentName(context), UserManager.DISALLOW_SAFE_BOOT);
// To enable safe boot again
// manager.clearUserRestriction(admin, UserManager.DISALLOW_SAFE_BOOT);
Log.i(TAG, "addUserRestriction is done");
}
}
}
}
This code is working fine and properly tested.
I hope this might help someone looking for similar answers.
Thank You.

Not able to launch Gallery from Android Instant app

I created an app which picks up images from images folder in emulator and displays selected image in my layout of application. When I converted my app to instant app, the same code that picks up images from image folder throws exception. I used runtime permissions READ_EXTERNAL_STORAGE.
My code:
private static final int PICK_IMAGE_REQUEST = 234;
Intent intent = new Intent();
intent.setType("*/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(intent, PICK_IMAGE_REQUEST);
Exception thrown:
java.lang.SecurityException: Not allowed to start activity Intent {
act=android.intent.action.GET_CONTENT typ=*/* }
Code for runtime permissions:
private static final int REQUEST_WRITE_STORAGE = 112;
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public boolean checkPermission()
{
int currentAPIVersion = Build.VERSION.SDK_INT;
if(currentAPIVersion>=android.os.Build.VERSION_CODES.M)
{
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale((Activity) MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
Toast.makeText(MainActivity.this,"Permission Denied...",Toast.LENGTH_LONG).show();
} else {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_WRITE_STORAGE);
return true;
}
return false;
} else {
return true;
}
} else {
return true;
}
}
Can anyone help me in solving this issue?
Instant Apps are blocked from accessing some features that regular Android apps can access. The exception:
java.lang.SecurityException: Not allowed to start activity Intent...
Means you have found one such feature. In this case it is Intent.ACTION_GET_CONTENT which is not currently allowed. Support for additional features such as this may be introduced over time.
In addition, Instant Apps do not have access to read or write from external storage so requesting access to READ_EXTERNAL_STORAGE will also not work or throw an exception. See here for a list of permissions that are supported by Instant Apps.

Settings.canDrawOverlays for API < 23

Since I can use Settings.canDrawOverlays to check if the user granted this permission on API >= 23, how I can check if the user have it on older APIS?
Does this permission is automically granted on API < 23 and no need to check for it?
Currently, I start my service only on API 23+ after the permission are granted.
#SuppressLint("NewApi")
public void checkDrawOverlayPermission() {
if(Build.VERSION.SDK_INT >= 23) {
/** check if we already have permission to draw over other apps */
if (!Settings.canDrawOverlays(this)) {
/** if not construct intent to request permission */
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
/** request permission via start activity for result */
startActivityForResult(intent, REQUEST_CODE);
} else {
startService(new Intent(this, ChatHeadService.class));
}
}
}
But what if the user is on API <= 22? How I can make sure the application wont crash and my service will start?
Overlay permission is only required for Marshmallow (API 23) and above. In previous APIs this permission is provided by default.

Categories

Resources