Notification listener started.

This commit is contained in:
jens 2021-03-28 20:33:44 +02:00
parent 0df5342036
commit 7894504791
17 changed files with 839 additions and 208 deletions

View File

@ -140,6 +140,7 @@
<activity android:name=".ActivityEditTriggerUrl" /> <activity android:name=".ActivityEditTriggerUrl" />
<activity android:name=".ActivityDisplayLongMessage" /> <activity android:name=".ActivityDisplayLongMessage" />
<activity android:name=".ActivityEditSendTextMessage" /> <activity android:name=".ActivityEditSendTextMessage" />
<activity android:name=".ActivityManageActionPlaySound" />
<activity android:name=".ActivityManageTimeFrame" /> <activity android:name=".ActivityManageTimeFrame" />
<activity android:name=".ActivityManageBrightnessSetting" /> <activity android:name=".ActivityManageBrightnessSetting" />
<activity android:name=".ActivityHelp" /> <activity android:name=".ActivityHelp" />

View File

@ -1,13 +1,17 @@
package com.jens.automation2; package com.jens.automation2;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.content.Context; import android.content.Context;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build;
import android.os.Looper; import android.os.Looper;
import android.service.notification.StatusBarNotification;
import android.util.Log; import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import com.google.android.gms.location.DetectedActivity; import com.google.android.gms.location.DetectedActivity;
import com.jens.automation2.location.LocationProvider;
import com.jens.automation2.location.WifiBroadcastReceiver; import com.jens.automation2.location.WifiBroadcastReceiver;
import com.jens.automation2.receivers.ActivityDetectionReceiver; import com.jens.automation2.receivers.ActivityDetectionReceiver;
import com.jens.automation2.receivers.BatteryReceiver; import com.jens.automation2.receivers.BatteryReceiver;
@ -16,6 +20,7 @@ import com.jens.automation2.receivers.ConnectivityReceiver;
import com.jens.automation2.receivers.HeadphoneJackListener; import com.jens.automation2.receivers.HeadphoneJackListener;
import com.jens.automation2.receivers.NfcReceiver; import com.jens.automation2.receivers.NfcReceiver;
import com.jens.automation2.receivers.NoiseListener; import com.jens.automation2.receivers.NoiseListener;
import com.jens.automation2.receivers.NotificationListener;
import com.jens.automation2.receivers.PhoneStatusListener; import com.jens.automation2.receivers.PhoneStatusListener;
import com.jens.automation2.receivers.ProcessListener; import com.jens.automation2.receivers.ProcessListener;
@ -24,6 +29,10 @@ import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import static com.jens.automation2.Trigger.triggerParameter2Split;
import static com.jens.automation2.receivers.NotificationListener.EXTRA_TEXT;
import static com.jens.automation2.receivers.NotificationListener.EXTRA_TITLE;
public class Rule implements Comparable<Rule> public class Rule implements Comparable<Rule>
{ {
@ -114,6 +123,7 @@ public class Rule implements Comparable<Rule>
{ {
return this.getName(); return this.getName();
} }
@SuppressLint("NewApi")
public String toStringLong() public String toStringLong()
{ {
String returnString = ""; String returnString = "";
@ -488,7 +498,7 @@ public class Rule implements Comparable<Rule>
{ {
if(oneTrigger.getTriggerParameter()) if(oneTrigger.getTriggerParameter())
{ {
if(com.jens.automation2.location.LocationProvider.getSpeed() < oneTrigger.getSpeed()) if(LocationProvider.getSpeed() < oneTrigger.getSpeed())
{ {
Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyWeAreSlowerThan) + " " + String.valueOf(oneTrigger.getSpeed()), 3); Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyWeAreSlowerThan) + " " + String.valueOf(oneTrigger.getSpeed()), 3);
return false; return false;
@ -496,7 +506,7 @@ public class Rule implements Comparable<Rule>
} }
else else
{ {
if(com.jens.automation2.location.LocationProvider.getSpeed() > oneTrigger.getSpeed()) if(LocationProvider.getSpeed() > oneTrigger.getSpeed())
{ {
Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyWeAreFasterThan) + " " + String.valueOf(oneTrigger.getSpeed()), 3); Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyWeAreFasterThan) + " " + String.valueOf(oneTrigger.getSpeed()), 3);
return false; return false;
@ -725,15 +735,91 @@ public class Rule implements Comparable<Rule>
} }
else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.notification)) else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.notification))
{ {
k if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
if(HeadphoneJackListener.isHeadsetConnected() != oneTrigger.getTriggerParameter())
return false;
else
if(oneTrigger.getHeadphoneType() != 2 && oneTrigger.getHeadphoneType() != HeadphoneJackListener.getHeadphoneType())
{ {
Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyWrongHeadphoneType), 3); String[] params = oneTrigger.getTriggerParameter2().split(triggerParameter2Split);
String myApp = params[0];
String myTitleDir = params[1];
String myTitle = params[2];
String myTextDir = params[3];
String myText;
if (params.length >= 5)
myText = params[4];
else
myText = "";
if(oneTrigger.getTriggerParameter())
{
// Check an active notification that is still there
boolean foundMatch = false;
for (StatusBarNotification sbn : NotificationListener.getInstance().getActiveNotifications())
{
String app = sbn.getPackageName();
String title = sbn.getNotification().extras.getString(EXTRA_TITLE);
String text = sbn.getNotification().extras.getString(EXTRA_TEXT);
if (!myApp.equals("-1"))
{
if (!app.equalsIgnoreCase(myApp))
continue;
}
if (myTitle.length() > 0)
{
if (!Miscellaneous.compare(myTitleDir, title, myTitle))
continue;
}
if (myText.length() > 0)
{
if (!Miscellaneous.compare(myTextDir, text, myText))
continue;
}
foundMatch = true;
}
if(!foundMatch)
return false; return false;
} }
else
{
// check a notification that is gone
if(NotificationListener.getLastNotification() != null)
{
if(!NotificationListener.getLastNotification().isCreated())
{
String app = NotificationListener.getLastNotification().getApp();
String title = NotificationListener.getLastNotification().getTitle();
String text = NotificationListener.getLastNotification().getText();
if (!myApp.equals("-1"))
{
if (!app.equalsIgnoreCase(myApp))
return false;
}
if (myTitle.length() > 0)
{
if (!Miscellaneous.compare(myTitleDir, title, myTitle))
return false;
}
if (myText.length() > 0)
{
if (!Miscellaneous.compare(myTextDir, text, myText))
return false;
}
}
else
return false;
}
}
}
} }
} }

View File

@ -176,6 +176,7 @@
<activity android:name=".ActivityManageStartActivity" /> <activity android:name=".ActivityManageStartActivity" />
<activity android:name=".ActivityManageNfc" /> <activity android:name=".ActivityManageNfc" />
<activity android:name=".ActivityEditSpeakText" /> <activity android:name=".ActivityEditSpeakText" />
<activity android:name=".ActivityManageActionPlaySound" />
<activity android:name=".ActivityManageBluetoothTrigger" /> <activity android:name=".ActivityManageBluetoothTrigger" />
<activity android:name=".ActivityMainProfiles" /> <activity android:name=".ActivityMainProfiles" />
<activity android:name=".ActivityManageProfile" /> <activity android:name=".ActivityManageProfile" />

View File

@ -732,6 +732,94 @@ public class Rule implements Comparable<Rule>
return false; return false;
} }
} }
else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.notification))
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
{
String[] params = oneTrigger.getTriggerParameter2().split(triggerParameter2Split);
String myApp = params[0];
String myTitleDir = params[1];
String myTitle = params[2];
String myTextDir = params[3];
String myText;
if (params.length >= 5)
myText = params[4];
else
myText = "";
if(oneTrigger.getTriggerParameter())
{
// Check an active notification that is still there
boolean foundMatch = false;
for (StatusBarNotification sbn : NotificationListener.getInstance().getActiveNotifications())
{
String app = sbn.getPackageName();
String title = sbn.getNotification().extras.getString(EXTRA_TITLE);
String text = sbn.getNotification().extras.getString(EXTRA_TEXT);
if (!myApp.equals("-1"))
{
if (!app.equalsIgnoreCase(myApp))
continue;
}
if (myTitle.length() > 0)
{
if (!Miscellaneous.compare(myTitleDir, title, myTitle))
continue;
}
if (myText.length() > 0)
{
if (!Miscellaneous.compare(myTextDir, text, myText))
continue;
}
foundMatch = true;
}
if(!foundMatch)
return false;
}
else
{
// check a notification that is gone
if(NotificationListener.getLastNotification() != null)
{
if(!NotificationListener.getLastNotification().isCreated())
{
String app = NotificationListener.getLastNotification().getApp();
String title = NotificationListener.getLastNotification().getTitle();
String text = NotificationListener.getLastNotification().getText();
if (!myApp.equals("-1"))
{
if (!app.equalsIgnoreCase(myApp))
return false;
}
if (myTitle.length() > 0)
{
if (!Miscellaneous.compare(myTitleDir, title, myTitle))
return false;
}
if (myText.length() > 0)
{
if (!Miscellaneous.compare(myTextDir, text, myText))
return false;
}
}
else
return false;
}
}
}
}
} }
return true; return true;

View File

@ -129,6 +129,7 @@
<activity android:name=".ActivityEditTriggerUrl" /> <activity android:name=".ActivityEditTriggerUrl" />
<activity android:name=".ActivityDisplayLongMessage" /> <activity android:name=".ActivityDisplayLongMessage" />
<activity android:name=".ActivityEditSendTextMessage" /> <activity android:name=".ActivityEditSendTextMessage" />
<activity android:name=".ActivityManageActionPlaySound" />
<activity android:name=".ActivityManageTimeFrame" /> <activity android:name=".ActivityManageTimeFrame" />
<activity android:name=".ActivityManageBrightnessSetting" /> <activity android:name=".ActivityManageBrightnessSetting" />
<activity android:name=".ActivityHelp" /> <activity android:name=".ActivityHelp" />

View File

@ -763,6 +763,94 @@ public class Rule implements Comparable<Rule>
return false; return false;
} }
} }
else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.notification))
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
{
String[] params = oneTrigger.getTriggerParameter2().split(triggerParameter2Split);
String myApp = params[0];
String myTitleDir = params[1];
String myTitle = params[2];
String myTextDir = params[3];
String myText;
if (params.length >= 5)
myText = params[4];
else
myText = "";
if(oneTrigger.getTriggerParameter())
{
// Check an active notification that is still there
boolean foundMatch = false;
for (StatusBarNotification sbn : NotificationListener.getInstance().getActiveNotifications())
{
String app = sbn.getPackageName();
String title = sbn.getNotification().extras.getString(EXTRA_TITLE);
String text = sbn.getNotification().extras.getString(EXTRA_TEXT);
if (!myApp.equals("-1"))
{
if (!app.equalsIgnoreCase(myApp))
continue;
}
if (myTitle.length() > 0)
{
if (!Miscellaneous.compare(myTitleDir, title, myTitle))
continue;
}
if (myText.length() > 0)
{
if (!Miscellaneous.compare(myTextDir, text, myText))
continue;
}
foundMatch = true;
}
if(!foundMatch)
return false;
}
else
{
// check a notification that is gone
if(NotificationListener.getLastNotification() != null)
{
if(!NotificationListener.getLastNotification().isCreated())
{
String app = NotificationListener.getLastNotification().getApp();
String title = NotificationListener.getLastNotification().getTitle();
String text = NotificationListener.getLastNotification().getText();
if (!myApp.equals("-1"))
{
if (!app.equalsIgnoreCase(myApp))
return false;
}
if (myTitle.length() > 0)
{
if (!Miscellaneous.compare(myTitleDir, title, myTitle))
return false;
}
if (myText.length() > 0)
{
if (!Miscellaneous.compare(myTextDir, text, myText))
return false;
}
}
else
return false;
}
}
}
}
} }
return true; return true;

View File

@ -12,6 +12,8 @@ import java.util.Locale;
public class Action public class Action
{ {
public static final String actionParameter2Split = "ap2split";
public enum Action_Enum { public enum Action_Enum {
setWifi, setWifi,
setBluetooth, setBluetooth,
@ -33,6 +35,7 @@ public class Action
speakText, speakText,
playMusic, playMusic,
setScreenBrightness, setScreenBrightness,
playSound,
sendTextMessage; sendTextMessage;
public String getFullName(Context context) public String getFullName(Context context)
@ -87,6 +90,8 @@ public class Action
return context.getResources().getString(R.string.actionSpeakText); return context.getResources().getString(R.string.actionSpeakText);
case playMusic: case playMusic:
return context.getResources().getString(R.string.actionPlayMusic); return context.getResources().getString(R.string.actionPlayMusic);
case playSound:
return context.getResources().getString(R.string.playSound);
case sendTextMessage: case sendTextMessage:
return context.getResources().getString(R.string.sendTextMessage); return context.getResources().getString(R.string.sendTextMessage);
case setScreenBrightness: case setScreenBrightness:
@ -210,6 +215,10 @@ public class Action
{ {
returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.wakeupDevice)); returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.wakeupDevice));
} }
else if(this.getAction().equals(Action_Enum.playMusic))
{
returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.playSound) + " " + getParameter2());
}
else else
returnString.append(action.toString()); returnString.append(action.toString());
@ -395,6 +404,9 @@ public class Action
case setScreenBrightness: case setScreenBrightness:
Actions.setScreenBrightness(getParameter1(), Integer.parseInt(getParameter2())); Actions.setScreenBrightness(getParameter1(), Integer.parseInt(getParameter2()));
break; break;
case playSound:
Actions.playSound(getParameter1(), getParameter2());
break;
default: default:
Miscellaneous.logEvent("w", "Action", context.getResources().getString(R.string.unknownActionSpecified), 3); Miscellaneous.logEvent("w", "Action", context.getResources().getString(R.string.unknownActionSpecified), 3);
break; break;

View File

@ -8,6 +8,7 @@ import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import android.os.Build; import android.os.Build;
@ -465,6 +466,26 @@ public class Actions
return ""; return "";
} }
public static void playSound(boolean alwaysPlay, String soundFileLocation)
{
if(alwaysPlay || ((AudioManager) Miscellaneous.getAnyContext().getSystemService(Context.AUDIO_SERVICE)).getRingerMode() == AudioManager.RINGER_MODE_NORMAL)
{
MediaPlayer mp = new MediaPlayer();
try
{
mp.setDataSource(soundFileLocation);
mp.prepare();
mp.start();
}
catch (Exception e)
{
Miscellaneous.logEvent("e", "Play sound file", "Error playing sound: " + Log.getStackTraceString(e), 2);
}
}
else
Miscellaneous.logEvent("i", "Play sound file", "Not playing sound file because phone is on some kind of mute state.", 2);
}
public void useDownloadedWebpage(String result) public void useDownloadedWebpage(String result)
{ {
// Toast.makeText(context, "Result: " + result, Toast.LENGTH_LONG).show(); // Toast.makeText(context, "Result: " + result, Toast.LENGTH_LONG).show();

View File

@ -0,0 +1,98 @@
package com.jens.automation2;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
public class ActivityManageActionPlaySound extends Activity
{
final static int PICKFILE_RESULT_CODE = 4711;
CheckBox chkPlaySoundAlwaysPlay;
TextView tvSelectedSoundFile;
Button bSelectSoundFile, bSavePlaySound;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_manage_play_sound);
chkPlaySoundAlwaysPlay = (CheckBox)findViewById(R.id.chkPlaySoundAlwaysPlay);
tvSelectedSoundFile = (TextView)findViewById(R.id.tvSelectedSoundFile);
bSelectSoundFile = (Button)findViewById(R.id.bSelectSoundFile);
bSavePlaySound = (Button)findViewById(R.id.bSavePlaySound);
boolean edit = getIntent().getBooleanExtra("edit", false);
if(edit)
{
boolean param1 = getIntent().getBooleanExtra("actionParameter1", false);
String param2 = getIntent().getStringExtra("actionParameter2");
chkPlaySoundAlwaysPlay.setChecked(param1);
tvSelectedSoundFile.setText(param2);
}
bSelectSoundFile.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
//Need to check for storage permissions
Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
chooseFile.setType("*/*");
chooseFile = Intent.createChooser(chooseFile, getResources().getString(R.string.selectSoundFile));
startActivityForResult(chooseFile, PICKFILE_RESULT_CODE);
}
});
bSavePlaySound.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
savePlaySoundSettings();
}
});
}
void savePlaySoundSettings()
{
if(tvSelectedSoundFile.getText().toString() == null || tvSelectedSoundFile.getText().toString().length() == 0)
{
Toast.makeText(ActivityManageActionPlaySound.this, getResources().getString(R.string.selectSoundFile), Toast.LENGTH_LONG).show();
return;
}
Intent returnData = new Intent();
returnData.putExtra("actionParameter1", chkPlaySoundAlwaysPlay.isChecked());
returnData.putExtra("actionParameter2", tvSelectedSoundFile.getText().toString());
setResult(RESULT_OK, returnData);
finish();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if(resultCode == RESULT_OK)
{
if(requestCode == PICKFILE_RESULT_CODE)
{
Uri fileUri = data.getData();
String filePath = fileUri.getPath();
tvSelectedSoundFile.setText(filePath);
}
}
}
}

View File

@ -97,6 +97,8 @@ public class ActivityManageRule extends Activity
final static int requestCodeActionSendTextMessage = 7001; final static int requestCodeActionSendTextMessage = 7001;
final static int requestCodeTriggerNotificationAdd = 8000; final static int requestCodeTriggerNotificationAdd = 8000;
final static int requestCodeTriggerNfcNotificationEdit = 8001; final static int requestCodeTriggerNfcNotificationEdit = 8001;
final static int requestCodeActionPlaySoundAdd = 501;
final static int requestCodeActionPlaySoundEdit = 502;
public static ActivityManageRule getInstance() public static ActivityManageRule getInstance()
{ {
@ -248,6 +250,12 @@ public class ActivityManageRule extends Activity
Intent bluetoothEditor = new Intent(ActivityManageRule.this, ActivityManageBluetoothTrigger.class); Intent bluetoothEditor = new Intent(ActivityManageRule.this, ActivityManageBluetoothTrigger.class);
startActivityForResult(bluetoothEditor, requestCodeTriggerBluetoothEdit); startActivityForResult(bluetoothEditor, requestCodeTriggerBluetoothEdit);
break; break;
case notification:
ActivityManageTriggerNotification.editedNotificationTrigger = selectedTrigger;
Intent notificationEditor = new Intent(ActivityManageRule.this, ActivityManageTriggerNotification.class);
notificationEditor.putExtra("edit", true);
startActivityForResult(notificationEditor, requestCodeTriggerNfcNotificationEdit);
break;
default: default:
break; break;
} }
@ -341,6 +349,13 @@ public class ActivityManageRule extends Activity
activityEditScreenBrightnessIntent.putExtra("brightnessValue", Integer.parseInt(a.getParameter2())); activityEditScreenBrightnessIntent.putExtra("brightnessValue", Integer.parseInt(a.getParameter2()));
startActivityForResult(activityEditScreenBrightnessIntent, requestCodeActionScreenBrightnessEdit); startActivityForResult(activityEditScreenBrightnessIntent, requestCodeActionScreenBrightnessEdit);
break; break;
case playSound:
Intent actionPlaySoundIntent = new Intent(context, ActivityManageActionPlaySound.class);
actionPlaySoundIntent.putExtra("edit", true);
actionPlaySoundIntent.putExtra("actionParameter1", a.getParameter1());
actionPlaySoundIntent.putExtra("actionParameter2", a.getParameter2());
startActivityForResult(actionPlaySoundIntent, requestCodeActionPlaySoundEdit);
break;
default: default:
Miscellaneous.logEvent("w", "Edit action", "Editing of action type " + a.getAction().toString() + " not implemented, yet.", 4); Miscellaneous.logEvent("w", "Edit action", "Editing of action type " + a.getAction().toString() + " not implemented, yet.", 4);
break; break;
@ -1170,6 +1185,14 @@ public class ActivityManageRule extends Activity
else else
Miscellaneous.logEvent("w", "ActivityManageNfc", "No nfc id returned. Assuming abort.", 5); Miscellaneous.logEvent("w", "ActivityManageNfc", "No nfc id returned. Assuming abort.", 5);
} }
else if(requestCode == requestCodeTriggerNfcNotificationEdit)
{
if(resultCode == RESULT_OK)
{
newTrigger = ActivityManageTriggerNotification.resultingTrigger;
this.refreshTriggerList();
}
}
else if(requestCode == requestCodeActionSpeakTextAdd) else if(requestCode == requestCodeActionSpeakTextAdd)
{ {
if(resultCode == RESULT_OK) if(resultCode == RESULT_OK)
@ -1284,6 +1307,8 @@ public class ActivityManageRule extends Activity
items.add(new Item(typesLong[i].toString(), R.drawable.tune)); items.add(new Item(typesLong[i].toString(), R.drawable.tune));
else if(types[i].toString().equals(Action_Enum.setScreenBrightness.toString())) else if(types[i].toString().equals(Action_Enum.setScreenBrightness.toString()))
items.add(new Item(typesLong[i].toString(), R.drawable.brightness)); items.add(new Item(typesLong[i].toString(), R.drawable.brightness));
else if(types[i].toString().equals(Action_Enum.playSound.toString()))
items.add(new Item(typesLong[i].toString(), R.drawable.sound));
else if(types[i].toString().equals(Action_Enum.sendTextMessage.toString())) else if(types[i].toString().equals(Action_Enum.sendTextMessage.toString()))
{ {
// if(ActivityPermissions.isPermissionDeclaratedInManifest(ActivityManageSpecificRule.this, "android.permission.SEND_SMS") && !Miscellaneous.isGooglePlayInstalled(ActivityManageSpecificRule.this)) // if(ActivityPermissions.isPermissionDeclaratedInManifest(ActivityManageSpecificRule.this, "android.permission.SEND_SMS") && !Miscellaneous.isGooglePlayInstalled(ActivityManageSpecificRule.this))
@ -1294,13 +1319,6 @@ public class ActivityManageRule extends Activity
items.add(new Item(typesLong[i].toString(), R.drawable.placeholder)); items.add(new Item(typesLong[i].toString(), R.drawable.placeholder));
} }
// = {
// new Item("Bluetooth", R.drawable.bluetooth),
// new Item("Wifi", R.drawable.wifi),
// new Item("...", 0), //no icon for this one
// };
// ListAdapter adapter = new ArrayAdapter<Item>(this, android.R.layout.select_dialog_item, android.R.id.text1, items)
ListAdapter adapter = new ArrayAdapter<Item>(this, android.R.layout.select_dialog_item, android.R.id.text1, items) ListAdapter adapter = new ArrayAdapter<Item>(this, android.R.layout.select_dialog_item, android.R.id.text1, items)
{ {
public View getView(int position, View convertView, ViewGroup parent) public View getView(int position, View convertView, ViewGroup parent)
@ -1442,6 +1460,12 @@ public class ActivityManageRule extends Activity
Intent actionScreenBrightnessIntent = new Intent(context, ActivityManageBrightnessSetting.class); Intent actionScreenBrightnessIntent = new Intent(context, ActivityManageBrightnessSetting.class);
startActivityForResult(actionScreenBrightnessIntent, requestCodeActionScreenBrightnessAdd); startActivityForResult(actionScreenBrightnessIntent, requestCodeActionScreenBrightnessAdd);
} }
else if(Action.getActionTypesAsArray()[which].toString().equals(Action_Enum.playSound.toString()))
{
newAction.setAction(Action_Enum.playSound);
Intent actionPlaySoundIntent = new Intent(context, ActivityManageActionPlaySound.class);
startActivityForResult(actionPlaySoundIntent, requestCodeActionPlaySoundAdd);
}
} }
}); });

View File

@ -29,8 +29,11 @@ import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import static com.jens.automation2.Trigger.triggerParameter2Split;
public class ActivityManageTriggerNotification extends Activity public class ActivityManageTriggerNotification extends Activity
{ {
public static Trigger editedNotificationTrigger;
EditText etNotificationTitle, etNotificationText; EditText etNotificationTitle, etNotificationText;
Button bSelectApp, bSaveTriggerNotification; Button bSelectApp, bSaveTriggerNotification;
Spinner spinnerTitleDirection, spinnerTextDirection; Spinner spinnerTitleDirection, spinnerTextDirection;
@ -312,6 +315,14 @@ public class ActivityManageTriggerNotification extends Activity
String textDir = Trigger.getMatchCode(spinnerTextDirection.getSelectedItem().toString()); String textDir = Trigger.getMatchCode(spinnerTextDirection.getSelectedItem().toString());
String text = etNotificationText.getText().toString(); String text = etNotificationText.getText().toString();
if(edit)
{
editedNotificationTrigger.setTriggerParameter(chkNotificationDirection.isChecked());
editedNotificationTrigger.setTriggerParameter2(app + triggerParameter2Split + titleDir + triggerParameter2Split + title + triggerParameter2Split + textDir + triggerParameter2Split + text);
ActivityManageTriggerNotification.this.setResult(RESULT_OK);
}
else
{
Intent data = new Intent(); Intent data = new Intent();
data.putExtra("direction", chkNotificationDirection.isChecked()); data.putExtra("direction", chkNotificationDirection.isChecked());
data.putExtra("app", app); data.putExtra("app", app);
@ -319,8 +330,9 @@ public class ActivityManageTriggerNotification extends Activity
data.putExtra("title", title); data.putExtra("title", title);
data.putExtra("textDir", textDir); data.putExtra("textDir", textDir);
data.putExtra("text", text); data.putExtra("text", text);
ActivityManageTriggerNotification.this.setResult(RESULT_OK, data); ActivityManageTriggerNotification.this.setResult(RESULT_OK, data);
}
finish(); finish();
} }
}); });
@ -335,23 +347,37 @@ public class ActivityManageTriggerNotification extends Activity
private void loadValuesIntoGui() private void loadValuesIntoGui()
{ {
// String[] params = resultingAction.getParameter2().split(";"); chkNotificationDirection.setChecked(editedNotificationTrigger.getTriggerParameter());
// if(params.length >= 2)
// { String[] params = editedNotificationTrigger.getTriggerParameter2().split(triggerParameter2Split);
// tvSelectedActivity.setText(params[0] + ";" + params[1]);
// String app = params[0];
// if(params.length > 2) String titleDir = params[1];
// { String title = params[2];
// intentPairList.clear(); String textDir = params[3];
// String text;
// for(int i=2; i<params.length; i++) if (params.length >= 5)
// { text = params[4];
// intentPairList.add(params[i]); else
// } text = "";
//
// updateIntentPairList(); if(!app.equals("-1"))
// } tvSelectedApplication.setText(app);
// }
for(int i = 0; i < directions.length; i++)
{
if(Trigger.getMatchCode(directions[i]).equalsIgnoreCase(titleDir))
spinnerTitleDirection.setSelection(i);
if(Trigger.getMatchCode(directions[i]).equalsIgnoreCase(textDir))
spinnerTextDirection.setSelection(i);
}
if(title.length() > 0)
etNotificationTitle.setText(title);
if(text.length() > 0)
etNotificationText.setText(text);
} }
private class GetActivityListTask extends AsyncTask<Void, Void, Void> private class GetActivityListTask extends AsyncTask<Void, Void, Void>

View File

@ -2,6 +2,7 @@ package com.jens.automation2;
import android.app.Activity; import android.app.Activity;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
@ -15,6 +16,8 @@ import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
import com.jens.automation2.receivers.NotificationListener;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
@ -34,12 +37,13 @@ public class ActivityPermissions extends Activity
private static final int requestCodeForPermissionsWriteSettings = 12043; private static final int requestCodeForPermissionsWriteSettings = 12043;
private static final int requestCodeForPermissionsNotificationPolicy = 12044; private static final int requestCodeForPermissionsNotificationPolicy = 12044;
private static final int requestCodeForPermissionsBackgroundLocation = 12045; private static final int requestCodeForPermissionsBackgroundLocation = 12045;
private static final int requestCodeForPermissionsNotifications = 12046;
protected String[] specificPermissionsToRequest = null; protected String[] specificPermissionsToRequest = null;
public static String intentExtraName = "permissionsToBeRequested"; public static String intentExtraName = "permissionsToBeRequested";
Button bCancelPermissions, bRequestPermissions; Button bCancelPermissions, bRequestPermissions;
TextView tvPermissionsExplanation, tvPermissionosExplanationSystemSettings, tvPermissionsExplanationLong; TextView tvPermissionsExplanation, tvPermissionsExplanationSystemSettings, tvPermissionsExplanationLong;
static ActivityPermissions instance = null; static ActivityPermissions instance = null;
public static final String writeSystemSettingsPermissionName = "android.permission.WRITE_SETTINGS"; public static final String writeSystemSettingsPermissionName = "android.permission.WRITE_SETTINGS";
@ -50,6 +54,7 @@ public class ActivityPermissions extends Activity
public static final String permissionNameLocationBackground = "android.permission.ACCESS_BACKGROUND_LOCATION"; public static final String permissionNameLocationBackground = "android.permission.ACCESS_BACKGROUND_LOCATION";
public static final String permissionNameCall = "android.permission.PROCESS_OUTGOING_CALLS"; public static final String permissionNameCall = "android.permission.PROCESS_OUTGOING_CALLS";
public static final String permissionNameStartService = "android.permission.FOREGROUND_SERVICE"; public static final String permissionNameStartService = "android.permission.FOREGROUND_SERVICE";
public static final String permissionNameReadNotifications = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
public static ActivityPermissions getInstance() public static ActivityPermissions getInstance()
{ {
@ -72,7 +77,7 @@ public class ActivityPermissions extends Activity
bCancelPermissions = (Button)findViewById(R.id.bCancelPermissions); bCancelPermissions = (Button)findViewById(R.id.bCancelPermissions);
bRequestPermissions = (Button)findViewById(R.id.bRequestPermissions); bRequestPermissions = (Button)findViewById(R.id.bRequestPermissions);
tvPermissionsExplanation = (TextView)findViewById(R.id.tvPermissionsExplanation); tvPermissionsExplanation = (TextView)findViewById(R.id.tvPermissionsExplanation);
tvPermissionosExplanationSystemSettings = (TextView)findViewById(R.id.tvPermissionsExplanationSystemSettings); tvPermissionsExplanationSystemSettings = (TextView)findViewById(R.id.tvPermissionsExplanationSystemSettings);
tvPermissionsExplanationLong = (TextView)findViewById(R.id.tvPermissionsExplanationLong); tvPermissionsExplanationLong = (TextView)findViewById(R.id.tvPermissionsExplanationLong);
bCancelPermissions.setOnClickListener(new View.OnClickListener() bCancelPermissions.setOnClickListener(new View.OnClickListener()
@ -168,8 +173,6 @@ public class ActivityPermissions extends Activity
explanation.append( explanation.append(
"<br />" + "<br />" +
"<u>" + "<u>" +
getResources().getString(R.string.readLocation) getResources().getString(R.string.readLocation)
+ "</u>" + "</u>"
@ -188,8 +191,6 @@ public class ActivityPermissions extends Activity
explanation.append( explanation.append(
"<br />" + "<br />" +
"<u>" + "<u>" +
getResources().getString(getResources().getIdentifier(s, "string", getPackageName())) getResources().getString(getResources().getIdentifier(s, "string", getPackageName()))
+ "</u>" + "</u>"
@ -208,9 +209,9 @@ public class ActivityPermissions extends Activity
if (s.equalsIgnoreCase(writeSystemSettingsPermissionName)) if (s.equalsIgnoreCase(writeSystemSettingsPermissionName))
{ {
if (requiredPerms.length == 1) if (requiredPerms.length == 1)
tvPermissionosExplanationSystemSettings.setText(getResources().getString(R.string.systemSettingsNote1)); tvPermissionsExplanationSystemSettings.setText(getResources().getString(R.string.systemSettingsNote1));
else if (requiredPerms.length > 1) else if (requiredPerms.length > 1)
tvPermissionosExplanationSystemSettings.setText(getResources().getString(R.string.systemSettingsNote1) + getResources().getString(R.string.systemSettingsNote2)); tvPermissionsExplanationSystemSettings.setText(getResources().getString(R.string.systemSettingsNote1) + getResources().getString(R.string.systemSettingsNote2));
break; break;
} }
@ -231,7 +232,19 @@ public class ActivityPermissions extends Activity
{ {
for (String s : getRequiredPermissions(false)) for (String s : getRequiredPermissions(false))
{ {
if(!s.equalsIgnoreCase(permissionNameLocationBackground) && !s.equalsIgnoreCase(permissionNameLocationFine) && !s.equalsIgnoreCase(permissionNameLocationCoarse) && Miscellaneous.googleToBlameForLocation(true)) if(
s.equalsIgnoreCase(permissionNameLocationBackground)
||
s.equalsIgnoreCase(permissionNameLocationFine)
||
s.equalsIgnoreCase(permissionNameLocationCoarse)
)
{
if (!Miscellaneous.googleToBlameForLocation(true))
if (!havePermission(s, context))
return true;
}
else
if (!havePermission(s, context)) if (!havePermission(s, context))
return true; return true;
} }
@ -256,6 +269,10 @@ public class ActivityPermissions extends Activity
else else
return true; return true;
} }
else if (s.equals(permissionNameReadNotifications))
{
return verifyNotificationPermission();
}
else else
{ {
int res = context.checkCallingOrSelfPermission(s); int res = context.checkCallingOrSelfPermission(s);
@ -300,7 +317,19 @@ public class ActivityPermissions extends Activity
for (String singlePermission : getPermissionsForRule(rule)) for (String singlePermission : getPermissionsForRule(rule))
if (!havePermission(singlePermission, workingContext)) if (!havePermission(singlePermission, workingContext))
{ {
if(!singlePermission.equalsIgnoreCase(permissionNameLocationBackground) && !singlePermission.equalsIgnoreCase(permissionNameLocationFine) && !singlePermission.equalsIgnoreCase(permissionNameLocationCoarse) && Miscellaneous.googleToBlameForLocation(true)) if(
singlePermission.equalsIgnoreCase(permissionNameLocationBackground)
||
singlePermission.equalsIgnoreCase(permissionNameLocationFine)
||
singlePermission.equalsIgnoreCase(permissionNameLocationCoarse)
)
{
if (!Miscellaneous.googleToBlameForLocation(true))
addToArrayListUnique(singlePermission, requiredPermissions);
}
else
addToArrayListUnique(singlePermission, requiredPermissions); addToArrayListUnique(singlePermission, requiredPermissions);
} }
} }
@ -425,6 +454,9 @@ public class ActivityPermissions extends Activity
addToArrayListUnique("android.permission.ACCESS_NETWORK_STATE", requiredPermissions); addToArrayListUnique("android.permission.ACCESS_NETWORK_STATE", requiredPermissions);
addToArrayListUnique("android.permission.ACCESS_WIFI_STATE", requiredPermissions); addToArrayListUnique("android.permission.ACCESS_WIFI_STATE", requiredPermissions);
break; break;
case notification:
addToArrayListUnique(permissionNameReadNotifications, requiredPermissions);
break;
default: default:
break; break;
} }
@ -600,6 +632,11 @@ public class ActivityPermissions extends Activity
break; break;
case "android.permission.WRITE_EXTERNAL_STORAGE": case "android.permission.WRITE_EXTERNAL_STORAGE":
usingElements.add(getResources().getString(R.string.storeSettings)); usingElements.add(getResources().getString(R.string.storeSettings));
break;
case permissionNameReadNotifications:
for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.notification))
usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName));
break; break;
case "com.google.android.gms.permission.ACTIVITY_RECOGNITION": case "com.google.android.gms.permission.ACTIVITY_RECOGNITION":
for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.activityDetection)) for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.activityDetection))
@ -783,6 +820,10 @@ public class ActivityPermissions extends Activity
requestPermissions(cachedPermissionsToRequest, true); requestPermissions(cachedPermissionsToRequest, true);
} }
} }
if (requestCode == requestCodeForPermissionsNotifications)
if(havePermission(permissionNameReadNotifications, ActivityPermissions.this))
requestPermissions(cachedPermissionsToRequest, true);
} }
} }
@ -842,6 +883,14 @@ public class ActivityPermissions extends Activity
startActivityForResult(intent, requestCodeForPermissionsNotificationPolicy); startActivityForResult(intent, requestCodeForPermissionsNotificationPolicy);
return; return;
} }
else if (s.equalsIgnoreCase(permissionNameReadNotifications))
{
requiredPermissions.remove(s);
cachedPermissionsToRequest = requiredPermissions;
Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
startActivityForResult(intent, requestCodeForPermissionsNotifications);
return;
}
// else if (s.equalsIgnoreCase(permissionNameLocationBackground) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) // else if (s.equalsIgnoreCase(permissionNameLocationBackground) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
// { // {
// requiredPermissions.remove(s); // requiredPermissions.remove(s);
@ -1392,4 +1441,17 @@ public class ActivityPermissions extends Activity
return false; return false;
} }
public static Boolean verifyNotificationPermission()
{
String theList = android.provider.Settings.Secure.getString(Miscellaneous.getAnyContext().getContentResolver(), "enabled_notification_listeners");
String[] theListList = theList.split(":");
String me = (new ComponentName(Miscellaneous.getAnyContext(), NotificationListener.class)).flattenToString();
for ( String next : theListList )
{
if ( me.equals(next) )
return true;
}
return false;
}
} }

View File

@ -287,8 +287,6 @@ public class AutomationService extends Service implements OnInitListener
myLocationProvider.applySettingsAndRules(); myLocationProvider.applySettingsAndRules();
ReceiverCoordinator.applySettingsAndRules(); ReceiverCoordinator.applySettingsAndRules();
Miscellaneous.createDismissableNotification("test", 4711, null);
} }
@Override @Override

View File

@ -441,6 +441,25 @@ public class Miscellaneous extends Service
return isEmulator; return isEmulator;
} }
public static boolean compare(String direction, String needle, String haystack)
{
switch(direction)
{
case Trigger.directionEquals:
return haystack.equalsIgnoreCase(needle);
case Trigger.directionNotEquals:
return !haystack.equalsIgnoreCase(needle);
case Trigger.directionContains:
return haystack.toLowerCase().contains(needle.toLowerCase());
case Trigger.directionStartsWith:
return haystack.toLowerCase().startsWith(needle.toLowerCase());
case Trigger.directionEndsWith:
return haystack.toLowerCase().endsWith(needle.toLowerCase());
default:
return false;
}
}
public static int compareTimes(Time time1, Time time2) public static int compareTimes(Time time1, Time time2)
{ {
// Miscellaneous.logEvent("i", "TimeCompare", "To compare: " + time1.toString() + " / " + time2.toString()); // Miscellaneous.logEvent("i", "TimeCompare", "To compare: " + time1.toString() + " / " + time2.toString());

View File

@ -1,8 +1,6 @@
package com.jens.automation2.receivers; package com.jens.automation2.receivers;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification; import android.service.notification.StatusBarNotification;
@ -21,6 +19,9 @@ import java.util.ArrayList;
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class NotificationListener extends NotificationListenerService public class NotificationListener extends NotificationListenerService
{ {
static NotificationListener instance;
static SimpleNotification lastNotification = null;
// the title of the notification, // the title of the notification,
public static final String EXTRA_TITLE = "android.title"; public static final String EXTRA_TITLE = "android.title";
@ -33,10 +34,21 @@ public class NotificationListener extends NotificationListenerService
// a bitmap to be used instead of the small icon when showing the notification payload // a bitmap to be used instead of the small icon when showing the notification payload
public static final String EXTRA_LARGE_ICON = "android.largeIcon"; public static final String EXTRA_LARGE_ICON = "android.largeIcon";
public static SimpleNotification getLastNotification()
{
return lastNotification;
}
@Override @Override
public void onCreate() public void onCreate()
{ {
super.onCreate(); super.onCreate();
instance = this;
}
public static NotificationListener getInstance()
{
return instance;
} }
@RequiresApi(api = Build.VERSION_CODES.KITKAT) @RequiresApi(api = Build.VERSION_CODES.KITKAT)
@ -46,13 +58,7 @@ public class NotificationListener extends NotificationListenerService
super.onNotificationPosted(sbn); super.onNotificationPosted(sbn);
if(AutomationService.isMyServiceRunning(NotificationListener.this)) if(AutomationService.isMyServiceRunning(NotificationListener.this))
{ checkNotification(true, sbn);
String app = sbn.getPackageName();
String title = sbn.getNotification().extras.getString(EXTRA_TITLE);
String text = sbn.getNotification().extras.getString(EXTRA_TEXT);
checkNotification(true, app, title, text);
}
} }
@RequiresApi(api = Build.VERSION_CODES.KITKAT) @RequiresApi(api = Build.VERSION_CODES.KITKAT)
@ -62,17 +68,23 @@ public class NotificationListener extends NotificationListenerService
super.onNotificationRemoved(sbn); super.onNotificationRemoved(sbn);
if(AutomationService.isMyServiceRunning(NotificationListener.this)) if(AutomationService.isMyServiceRunning(NotificationListener.this))
checkNotification(false, sbn);
}
boolean checkNotification(boolean created, StatusBarNotification sbn)
{
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT)
{ {
String app = sbn.getPackageName(); String app = sbn.getPackageName();
String title = sbn.getNotification().extras.getString(EXTRA_TITLE); String title = sbn.getNotification().extras.getString(EXTRA_TITLE);
String text = sbn.getNotification().extras.getString(EXTRA_TEXT); String text = sbn.getNotification().extras.getString(EXTRA_TEXT);
checkNotification(true, app, title, text); lastNotification = new SimpleNotification();
} lastNotification.created = created;
} lastNotification.app = app;
lastNotification.title = title;
lastNotification.text = text;
void checkNotification(boolean created, String appName, String title, String text)
{
ArrayList<Rule> ruleCandidates = Rule.findRuleCandidates(Trigger.Trigger_Enum.notification); ArrayList<Rule> ruleCandidates = Rule.findRuleCandidates(Trigger.Trigger_Enum.notification);
for(int i=0; i<ruleCandidates.size(); i++) for(int i=0; i<ruleCandidates.size(); i++)
{ {
@ -81,6 +93,55 @@ public class NotificationListener extends NotificationListenerService
} }
} }
return false;
}
public static class SimpleNotification
{
boolean created;
String app, title, text;
public boolean isCreated()
{
return created;
}
public void setCreated(boolean created)
{
this.created = created;
}
public String getApp()
{
return app;
}
public void setApp(String app)
{
this.app = app;
}
public String getTitle()
{
return title;
}
public void setTitle(String title)
{
this.title = title;
}
public String getText()
{
return text;
}
public void setText(String text)
{
this.text = text;
}
}
@Override @Override
public void onListenerConnected() public void onListenerConnected()
{ {
@ -92,10 +153,4 @@ public class NotificationListener extends NotificationListenerService
{ {
super.onListenerDisconnected(); super.onListenerDisconnected();
} }
public static void openNotificationAccessWindow(Context context)
{
Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
context.startActivity(intent);
}
} }

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_margin="@dimen/default_margin">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="40dp"
android:layout_marginBottom="@dimen/default_margin"
android:text="@string/playSound" />
<CheckBox
android:id="@+id/chkPlaySoundAlwaysPlay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/alwaysPlay" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/alwaysPlayExplanation" />
<TextView
android:id="@+id/tvSelectedSoundFile"
android:layout_marginVertical="@dimen/default_margin"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/bSelectSoundFile"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/selectSoundFile" />
<Button
android:id="@+id/bSavePlaySound"
android:layout_marginTop="@dimen/default_margin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/save" />
</LinearLayout>

View File

@ -626,4 +626,9 @@
<string name="notificationAppears">Notification appears</string> <string name="notificationAppears">Notification appears</string>
<string name="notificationDisappears">Notification disappears</string> <string name="notificationDisappears">Notification disappears</string>
<string name="direction">Direction</string> <string name="direction">Direction</string>
<string name="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">Read system notifications</string>
<string name="playSound">Play sound</string>
<string name="alwaysPlay">always play</string>
<string name="alwaysPlayExplanation">If this settings is active the sound will always be played. If it is deactivated it will only play if your phone is neither on mute nor vibrate.\nHowever if active it will not have an effect on the volume. So if your phone is on ring mode it will not increase the media volume for example.</string>
<string name="selectSoundFile">Select sound file</string>
</resources> </resources>