Android request ACCESS_NOTIFICATION_POLICY and mute phone - android

I am a beginner Android developer and I have an interesting question at hand. I am trying to mute the phone with
AudioManager audioManager = (AudioManager) getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
audioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
But I am getting the following error
java.lang.SecurityException: Not allowed to change Do Not Disturb state
Now I do not understand this, as I am using EasyPermissions (https://github.com/googlesamples/easypermissions) and requesting the permission
Manifest.permission.ACCESS_NOTIFICATION_POLICY
But it does not ask me to allow anything on app startup. I figured this is because ACCESS_NOTIFICATION_POLICY is a non dangerous permission and thus granted at installation time, for which I also added it to my manifest.xml as thus
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
But it is still not working, my app is crashing because it throws the "Not allowed to change to Do Not Disturb state" error. Wierdly I found that I can request to go to the "Do Not Disturb access" screen with the following code
Intent intent = new Intent(
android.provider.Settings
.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS);
startActivity(intent);
What am I doing wrong here? Why cannot I request this permissions as a normal permission? Do I really have to go through the intent to allow my app to mute the phone?

From the AudioManager#setRingerMode() reference:
From N onward, ringer mode adjustments that would toggle Do Not Disturb are not allowed unless the app has been granted Do Not Disturb Access.
From API level 23 and onward, you have to declare ACCESS_NOTIFICATION_POLICY permission in the manifest AND then the user needs to grant your app access to toggle Do Not Disturb. You can check if the access is granted with NotificationManager#isNotificationPolicyAccessGranted(). If your package do not have access, start an ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS intent so the user can give your app access.
NotificationManager n = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
if(n.isNotificationPolicyAccessGranted()) {
AudioManager audioManager = (AudioManager) getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
audioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
}else{
// Ask the user to grant access
Intent intent = new Intent(android.provider.Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS);
startActivityForResult(intent);
}

I will post an answer on how I managed to solve this issue for androids over 23 and under 23 with a startActivityForResult callback
the requestMutePhonePermsAndMutePhone() function is called in the main actions onCreate function.
Mind you the code is very much intrusive, since the Settings menu is permacalled until you accept the Do Not Disturb permissions, but one can easily accomodate this code for their personal needs and maybe build a question prompt, etc...
private void requestMutePhonePermsAndMutePhone() {
try {
if (Build.VERSION.SDK_INT < 23) {
AudioManager audioManager = (AudioManager) getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
audioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
} else if( Build.VERSION.SDK_INT >= 23 ) {
this.requestDoNotDisturbPermissionOrSetDoNotDisturbApi23AndUp();
}
} catch ( SecurityException e ) {
}
}
private void requestDoNotDisturbPermissionOrSetDoNotDisturbApi23AndUp() {
//TO SUPPRESS API ERROR MESSAGES IN THIS FUNCTION, since Ive no time to figrure our Android SDK suppress stuff
if( Build.VERSION.SDK_INT < 23 ) {
return;
}
NotificationManager notificationManager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
if ( notificationManager.isNotificationPolicyAccessGranted()) {
AudioManager audioManager = (AudioManager) getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
audioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
} else{
// Ask the user to grant access
Intent intent = new Intent(android.provider.Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS);
startActivityForResult( intent, MainActivity.ON_DO_NOT_DISTURB_CALLBACK_CODE );
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// Check which request we're responding to
if (requestCode == MainActivity.ON_DO_NOT_DISTURB_CALLBACK_CODE ) {
this.requestDoNotDisturbPermissionOrSetDoNotDisturbApi23AndUp();
}
}

Related

Call from foreground service stops working when screen is off

I have the following code that makes a phone call:
public static void CallPhoneNumber(this Context context, string phoneNumber)
{
var uri = Android.Net.Uri.Parse("tel:" + phoneNumber);
var callIntent = new Intent(Intent.ActionCall, uri);
callIntent.AddFlags(ActivityFlags.NewTask);
callIntent.AddFlags(ActivityFlags.FromBackground);
context.StartActivity(callIntent);
}
I make a phone call inside a running foreground service. Basically the service detects conditions (in my case GPS location) and makes a phone call. It worked just fine with my Pixel 2XL and Android 9. But after upgrade to Android 10 I faced to a new problem.
First of all, I was forced to add a new permission FOREGROUND_SERVICE. Added, the foreground service works as expected and makes phone calls - but only when phone is "active", I mean it is not in a "sleep" mode when the screen is turned off.
If the screen is off - the service works, I can track the activity, but it doesn't make a phone call.
The adb logcat shows this warning (first line is Info, the second is Warning):
02-04 20:48:00.923 1315 7951 I ActivityTaskManager: START u0 {act=android.intent.action.CALL dat=tel:xxxxxxxxxxxx flg=0x10000004 cmp=com.android.server.telecom/.components.UserCallActivity} from uid 10174
02-04 20:48:00.924 1315 7951 W ActivityTaskManager: Background activity start [callingPackage: MyApp; callingUid: 10175; isCallingUidForeground: false; isCallingUidPersistentSystemProcess: false; realCallingUid: 10174; isRealCallingUidForeground: false; isRealCallingUidPersistentSystemProcess: false; originatingPendingIntent: null; isBgStartWhitelisted: false; intent: Intent { act=android.intent.action.CALL dat=tel:xxxxxxxxxxxx flg=0x10000004 cmp=com.android.server.telecom/.components.UserCallActivity }; callerApp: ProcessRecord{43f3a72 13957:MyApp/u0a174}]
I think you should take a look at "partial wakelock" to prevent the "sleep mode" of the phone.
https://developer.android.com/training/scheduling/wakelock
in your MainActivity.cs create a WakeLock object and under your OnCreate function configure your wakelock :
private PowerManager.WakeLock _wl = null;
protected override void OnCreate(Bundle savedInstanceState)
{
// ... Your own code here
PowerManager pmanager = (PowerManager)this.GetSystemService("power");
_wl = pmanager.NewWakeLock(WakeLockFlags.Partial, "myapp_wakelock");
_wl?.SetReferenceCounted(false);
_wl?.Acquire();
}
public override void OnDestroy()
{
_wl?.Release();
base.OnDestroy();
}
Don't forget to add a permission in your AndroidManifest.xml:
<uses-permission android:name="android.permission.WAKE_LOCK" />
This solution can work but you're need to be carreful because for some manufacturer, if your app use to many ressources the foreground service responsible of the state "always alive" of the CPU can be killed. In some cases, they add a "battery saver" option mode and you need to disable it directly in settings to run your app without issues.
Hope this can help
Use TelecomManager. Something like this:
Uri uri = Uri.fromParts("tel", phoneNumber, null);
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
TelecomManager telecomManager = (TelecomManager) getInstance().getSystemService(Context.TELECOM_SERVICE);
telecomManager.placeCall(uri, new Bundle());
}
else
{
Intent callIntent = new Intent(Intent.ACTION_CALL, uri);
callIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(callIntent);
}
}

How to start a activity from a broadcast receiver when the screen is locked in Android Q

I am trying to implement an alarm based application in Android Q using a broadcast receiver. I am running a foreground service using notification for triggering the alarm broadcast receiver. The service is working fine and it is also triggering the broadcast receiver. If we close the application or lock the screen after setting an alarm, the service will be running in the foreground with a notification.
When the alarm broadcast is called I am trying to open a new activity when the screen is locked to provide the functionality to stop the alarm and service. I tried disabling the keyguard, turn on the screen and then opening the activity from the broadcast receiver, but I couldn't succeed.
I tried using WindowManager flags but they are deprecated and do not make any difference in the code.
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
Since I am trying to start an activity from a BroadcastReceiver I won't be having any Activity to use KeyguardManager.requestDismissKeyguard(Activity activity, KeyguardDismissCallback callback)
Is there any way to start an activity when the screen is locked to turn off the alarm. My implementation is given as follows,
I also added permissions in the manifest file.
AndroidManifest.xml
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
AlarmBroadcastReceiver.class
public class AlarmBroadcastReceiver extends BroadcastReceiver {
public static MediaPlayer mp;
public static Vibrator vibrator;
private boolean isVibrationEnabled = false;
#Override
public void onReceive(Context context, Intent intent) {
long[] mVibratePattern = new long[]{0, 400, 400, 400, 400, 400, 400, 400};
final int[] mAmplitudes = new int[]{0, 128, 0, 128, 0, 128, 0, 128};
isVibrationEnabled = intent.getExtras().getBoolean(LocationAlertService.IS_VIBRATE);
mp=MediaPlayer.create(context, R.raw.ring1);
mp.setLooping(true);
mp.start();
if(isVibrationEnabled) {
vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrator.vibrate(VibrationEffect.createWaveform(mVibratePattern, mAmplitudes, 0));
} else {
//deprecated in API 26
vibrator.vibrate(mVibratePattern, 3);
}
}
Intent wakeIntent = new Intent(context, WakeUpActivity.class);
wakeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(wakeIntent);
}
}
I know that I am missing something. I would be happy if there are any suggestions to overcome the issue which I am facing. Thanks in advance for helping me out.
With Android Q, it is impossible to start an activity from background automatically if your app is not includes those exceptions listed in the link below. You can choose just show a service notification, and start pending intent with click.
https://developer.android.com/guide/components/activities/background-starts
To make the system work. The most possible solution in my view is adding "SYSTEM_ALERT_WINDOW" to manifest file. And ask for user permission once when the app opened 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:
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
}
}
}

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).

Program do not disturb mode

When I open my second activity, I would like it to turn on Do Not Disturb mode on my android device. However, I want Do Not Disturb to only turn on through the second page (either by opening it or through a button created on the second page). The only code I found on stack overflow was in my Android Manifest File
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
I'm not sure where to go from here, any help is appreciated.
Use that method:
private void setRingerMode(Context context, int mode) {
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
// Check for DND permissions for API 24+
if (android.os.Build.VERSION.SDK_INT < 24 || (android.os.Build.VERSION.SDK_INT >= 24 && !nm.isNotificationPolicyAccessGranted())) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
audioManager.setRingerMode(mode);
 }
}
Where mode parameter can be AudioManager.RINGER_MODE_SILENT or
AudioManager.RINGER_MODE_NORMAL

Permissions needed for NotificationManager

I'm trying to set the Ringer to Silent and Do not Disturb to Priority Only using the following
AudioManager myAudioMgr = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
NotificationManager myNOtificationMgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
myAudioMgr.setRingerMode(AudioManager.RINGER_MODE_SILENT);
myNOtificationMgr.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
I keep getting a security error
java.lang.RuntimeException: Unable to start receiver MyBroadcastReceiver: java.lang.SecurityException: Notification policy access denied
I've added the Access Notification Policy permission to my Manifest file
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
Am I missing an additional permission?
It seems like the user needs to explicity grant the permission to app via the settings screen in order for the app to manipulate the priority/silent via the notifications api. I am assuming that you are using the notificationManager class for this. Some links that might help you:
https://developer.android.com/reference/android/provider/Settings.html#ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS
https://developer.android.com/reference/android/app/NotificationManager.html#ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED
I think essentially what you need to do is direct the user to the "Show Do Not Disturb access settings" and have him enable the option for notification management (Something similar for what would you do for mock location for example)
Sample code:
startActivityForResult(new Intent(android.settings.NOTIFICATION_POLICY_ACCESS_SETTINGS), 0);
Hope this helps.
From Android Developers:
Request policy access by sending the user to the activity that matches
the system intent action ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS.
Use ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED to listen for
user grant or denial of this access.
So, to get permission you should request policy access by sending the user to the activity that matches the system intent action ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS and listen for response using BroadcastReceiver with action ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED.
Also take a look at method isNotificationPolicyAccessGranted() from NotificationManager to check if permission granted.
You'll need to do something like
public class SomeFragment extends FragmentCompat {
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
int permissionNotifications = ContextCompat.checkSelfPermission(getActivity(), Manifest.permission. ACCESS_NOTIFICATION_POLICY);
if (permissionNotifications != PackageManager.PERMISSION_GRANTED ) {
ActivityCompat.requestPermissions(
getActivity(),
new String[] { Manifest.permission.ACCESS_NOTIFICATION_POLICY },
PERMISSION_REQUEST
);
}
}
}
keep in mind I'm not able to run this code so it may require some TLC but it's a start
To correct the java.lang.SecurityException: Access denied to process: 3454 error, android.permission.SET_WALLPAPER permission must be added to the Android Manifest.
Add the following line in your AndroidManifest.xml:
uses-permission android:name="android.permission.SET_WALLPAPER"

Categories

Resources