8.- Source code.
package com.kio4.servicio;
// Juan A. Villalpando
// http://kio4.com/appinventor/147_extension_servicio_rush.htm
// https://www.geeksforgeeks.org/services-in-android-with-example/
// https://stackoverflow.com/questions/37568246/how-to-turn-off-doze-mode-for-specific-apps-in-marshmallow-devices-programmatica
// Compilado con Rush.
import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.annotations.SimpleEvent;
import com.google.appinventor.components.runtime.AndroidNonvisibleComponent;
import com.google.appinventor.components.runtime.ComponentContainer;
import com.google.appinventor.components.annotations.DesignerProperty;
import com.google.appinventor.components.annotations.SimpleProperty;
import com.google.appinventor.components.annotations.PropertyCategory;
import com.google.appinventor.components.common.PropertyTypeConstants;
import com.google.appinventor.components.annotations.UsesServices;
import com.google.appinventor.components.runtime.*;
import android.content.Context;
import android.content.Intent;
// BATTERY
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.content.BroadcastReceiver;
// COPY FILE FROM ASSET
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.BufferedOutputStream;
import android.content.pm.PackageManager;
// DOZE MODE
import android.net.Uri;
import android.os.PowerManager;
import android.provider.Settings;
import android.os.Build;
// NOTIFICACION
import android.R;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.NotificationChannel;
import android.app.PendingIntent;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationCompat;
// LargeIcon Bitmap
import android.graphics.BitmapFactory;
import android.graphics.Bitmap;
// TIMER
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import android.os.CountDownTimer;
public class Servicio extends AndroidNonvisibleComponent {
private ComponentContainer container;
public static Context context;
private String actual = "----";
private boolean running = false;
private CountDownTimer cTimer = null;
private int nivel = 0;
private boolean activado = false;
private boolean IgnoringBatteryOptimization = false;
private int notificationId;
private Bitmap LargeIcon;
private String CHANNEL_ID = "com.kio4.notificacion";
private static final String DEFAULT_SIGNAL = "";
private String signal = "";
public Servicio(ComponentContainer container) {
super(container.$form());
this.container = container;
context = (Context) container.$context();
Signal(DEFAULT_SIGNAL);
}
// Obtener el valor de la Propiedad signal. GET.
@SimpleProperty(description = "Get signal value.")
public String Signal() {return signal;}
// Establecer el valor de la Propiedad signal. SET.
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING, defaultValue = Servicio.DEFAULT_SIGNAL + "")
@SimpleProperty(description = "Set signal value for tap notification.")
public void Signal(String nuevoSignal) {this.signal = nuevoSignal;}
/////////////////// START //////////////////////////////////////////
@SimpleFunction(description = "Start Service.")
public void Start() {
if (!activado){
// Copia silencio.mp3 y cara.png desde el asset de la extension al ASD.
try {
CopyFileAsset("com.kio4.servicio/silencio.mp3", GetAsdPath() + "/silencio.mp3");
CopyFileAsset("com.kio4.servicio/cara.png", GetAsdPath() + "/cara.png");
LargeIcon = BitmapFactory.decodeFile(GetAsdPath() + "/cara.png");
}
catch (Exception e) { }
Intent intent = new Intent(context, NewService.class);
context.startService(intent);
activado = true;
////////////////////// DEVUELVE UN TEXTO CUANDO SE PULSA LA NOTIFICACION
BroadcastReceiver receiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("action_call_method")) {
NotificationSignal(signal); // Desde aqui va al EVENTO y devuelve signal.
}
}
};
IntentFilter filter = new IntentFilter("action_call_method");
context.registerReceiver(receiver, filter);
}
}
/////// EVENTO Retorno de Notificacion.
@SimpleEvent(description = "Return notificacion.")
public void NotificationSignal(String signal){
EventDispatcher.dispatchEvent(this, "NotificationSignal", signal);
}
/////////////////// STOP //////////////////////////////////////////
@SimpleFunction(description = "Stop Service.")
public void Stop() {
Intent intent = new Intent(context, NewService.class);
context.stopService(intent);
activado = false;
}
/////////////////////////////////////////////////////////////////////////
// Copia archivo desde el asset al ASD.
public void CopyFileAsset(String fileName, String dest) throws Exception {
InputStream stream = null;
OutputStream output = null;
stream = context.getAssets().open(fileName);
output = new BufferedOutputStream(new FileOutputStream(dest));
byte data[] = new byte[1024];
int count;
while((count = stream.read(data)) != -1)
{ output.write(data, 0, count); }
output.close();
stream.close();
}
//////////////////////////////// GET ASD PATH ///////////
// https://www.programcreek.com/java-api-examples/?class=android.content.Context&method=getExternalFilesDir
public String GetAsdPath() {
if (!context.getExternalFilesDir(null).exists()){context.getExternalFilesDir(null).mkdirs();} // Crea directorio files si no existiera.
return context.getExternalFilesDir(null).getAbsolutePath();
}
////////////////////// TURN OFF DOZE MODE ////////////////////
@SimpleFunction(description = "Turn off doze mode.")
public void TurnOffDozeMode(){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Intent intent = new Intent();
String packageName = context.getPackageName();
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (pm.isIgnoringBatteryOptimizations(packageName)) {// if you want to disable doze mode for this package
intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
}
else { // if you want to enable doze mode
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + packageName));
}
context.startActivity(intent);
}
}
/////////////////////// CHECK BATTERY OPTIMIZATION ///////////////////////////////////
@SimpleFunction(description = "Check if it is ignoring Battery Optimizations.")
public boolean BatteryOptimization(){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
String packageName = context.getPackageName();
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (pm.isIgnoringBatteryOptimizations(packageName)) {IgnoringBatteryOptimization = true; }
else {IgnoringBatteryOptimization = false;}
}
return IgnoringBatteryOptimization;
}
///////////////////// BATERIA NIVEL ///////////////////////////////////
@SimpleFunction(description = "Battery level (percent).")
public int Battery() {
IntentFilter iFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = context.registerReceiver(null, iFilter);
int level = batteryStatus != null ? batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) : -1;
int scale = batteryStatus != null ? batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1) : -1;
float batteryPct = level/(float) scale;
return (int) (batteryPct * 100);
}
///////////////////////////////////////////// NOTIFY /////////////////////////////
@SimpleFunction(description = "Notify in status bar.")
public void Notify(String title, String text){
final Intent intentNotification = new Intent("action_call_method");
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intentNotification, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builderNotificationCompat = new NotificationCompat.Builder(context, CHANNEL_ID)
.setContentIntent(pendingIntent)
.setContentTitle(title)
.setContentText(text)
.setSmallIcon(android.R.drawable.btn_star_big_on)
.setLargeIcon(LargeIcon)
.setAutoCancel(true); // Notification will remove when tap.
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
// Create Notification Channel API 26+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, "Notificacion", NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(notificationChannel);
}
notificationManager.notify(0,builderNotificationCompat.build());
}
//////////////////////////// To Background. Minimize //////////////////////////
// https://stackoverflow.com/questions/7530407/how-to-minimize-whole-application-in-android
@SimpleFunction(description = "To background. Minimize this screen.")
public void ToBackground() {
Intent startMain = new Intent(Intent.ACTION_MAIN);
startMain.addCategory(Intent.CATEGORY_HOME);
startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(startMain);
}
///////////////////////////////////////////////////////////////////////////
//////////////////////////// TIMER //////////////////////////
// Contador decremental
@SimpleFunction(description = "Interval and tick in milliseconds.")
public void Timer(int interval, int tick) {
CancelTimer();
cTimer = new CountDownTimer(interval, tick) {
public void onTick(long millisUntilFinished) {
actual = Long.toString(millisUntilFinished);
running = true;
TimerNow(actual, running);
}
public void onFinish() { // Esto se realiza cuando se termina.
running = false;
TimerNow(actual, running); // Toque final.
}
};
cTimer.start();
}
/////////////////////////////////////////////////////////////////////////////////////////////
// CANCELACION DEL TIMER
@SimpleFunction(description = "Cancel Timer.")
public void CancelTimer() {
if(cTimer!=null)
cTimer.cancel();
}
/////////////////////// EVENTO PARA EL TIMER /////////////////////////
@SimpleEvent(description = "Number, is actual count of timer. Running is true or false.")
public void TimerNow(String number, boolean running) {
EventDispatcher.dispatchEvent(this, "TimerNow", number, running);
}
} // => Fin
///////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
NewService.java
package com.kio4.servicio;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;
import android.provider.Settings;
import androidx.annotation.Nullable;
import android.net.Uri;
public class NewService extends Service {
// declaring object of MediaPlayer
private MediaPlayer player;
@Override
// execution of service will start
// on calling this method
public int onStartCommand(Intent intent, int flags, int startId) {
// creating a media player which
// will play the audio of Default
// ringtone in android device
// player = MediaPlayer.create(this, Settings.System.DEFAULT_RINGTONE_URI );
player = MediaPlayer.create(this, Uri.parse("file://"+ GetFileAsd("silencio.mp3")));
// providing the boolean
// value as true to play
// the audio on loop
player.setLooping(true);
// starting the process
player.start();
// returns the status
// of the program
return START_STICKY;
}
@Override
// execution of the service will
// stop on calling this method
public void onDestroy() {
super.onDestroy();
// stopping the process
player.stop();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
//////////////////////////////// GET PATH of silencio.mp3 ///////////
public String GetFileAsd(String NameFile) {
return Servicio.context.getExternalFilesDir(null).getAbsolutePath() + "/" + NameFile;
}
} // => Fin