Search code examples
javaandroidservice

How to reboot app when app is destroyed after some time using foregroundservice?


I built an app on Android studio using Java. I lock the app using a button and wake the screen up after 10 seconds. And also, I would like to restart the app using a foreground service when user closed the app after 1 minute. However, I could not manage where to run in my service. triggerRebirth() does work at first, however it does not work any onclick operation is done or when I use triggerRebirth() in a handler to run after 1 minute. Any help will be useful. Thanks!

First of all, I added permissions on AndroidManifest.xml

    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme.FullScreen">

        <service
            android:name=".services.RestartAppService"
            android:enabled="true"
            android:exported="false"
            android:stopWithTask="true" />

        <service
            android:name=".services.NotificationService"
            android:enabled="true"
            android:exported="false"
            android:stopWithTask="true" />

        <activity
            android:name=".Demo"
            android:theme="@style/AppTheme.FullScreen">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver
            android:name=".Controller"
            android:description="@string/app_description"
            android:label="@string/app_name"
            android:permission="android.permission.BIND_DEVICE_ADMIN">
            <meta-data
                android:name="android.app.device_admin"
                android:resource="@xml/device_admin" />

            <intent-filter>
                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
            </intent-filter>
        </receiver>
    </application>

my MainActivity is like Demo.java

package com.example.demo;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;

import android.app.AlertDialog;
import android.os.Handler;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
import android.os.PowerManager;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.Toast;

import com.example.demo.services.NotificationService;
import com.example.demo.services.RestartAppService;
import com.jakewharton.processphoenix.ProcessPhoenix;

import java.util.Timer;
import java.util.TimerTask;

import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;

public class Demo extends AppCompatActivity {

    //Buttons for lock the screen and enable the app as device administrator
    Button lockButton;
    static final int RESULT_ENABLE = 1;
    DevicePolicyManager devicePolicyManager;
    ComponentName componentName;
    Activity activity = this;
    public static boolean isAppClosed;
    Intent restartAppServiceIntent;
    boolean isScreenLocked;

    @RequiresApi(api = Build.VERSION_CODES.O_MR1)
    @SuppressLint({"SetTextI18n", "InvalidWakeLockTag"})
    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (ProcessPhoenix.isPhoenixProcess(this)) {
            return;
        }
        isAppClosed = false;
        //Locate button on xml
        lockButton = findViewById(R.id.sleepButton);
        //Create a policy manager and a system service for admin authorization
        devicePolicyManager = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
        componentName = new ComponentName(Demo.this, Controller.class);
        isScreenLocked = false;

        boolean active = devicePolicyManager.isAdminActive(componentName);

        if (active) {
            //App has admin authorization
            lockButton.setVisibility(View.VISIBLE);
        } else {
            //App does not have admin authorization
            lockButton.setVisibility(View.GONE);
            enableAdminAuthorization();
        }

        restartAppServiceIntent = new Intent(Demo.this, RestartAppService.class);

        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
            activity.startForegroundService(new Intent(Demo.this, NotificationService.class));
        } else {
            startService(new Intent(Demo.this, NotificationService.class));
        }

        //onClickListener for locking the screen and waking up the screen
        lockButton.setOnClickListener(v -> {
                    // Lock the screen
                    devicePolicyManager.lockNow();
                    isScreenLocked = true;
                    // Unlock and turn on screen
                    turnScreenOnThroughKeyguard(Demo.this);
                }
        );
    }

    private void enableAdminAuthorization() {
        boolean active1 = devicePolicyManager.isAdminActive(componentName);
        if (active1) {
            //App is not authorized and lock button is not visible, thus locking error without
            // permission is prevented
            devicePolicyManager.removeActiveAdmin(componentName);
            lockButton.setVisibility(View.GONE);
        } else {
            //Create an intent to add app as device admin and start its activity
            Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
            intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, componentName);
            intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, "Please enable the app as device administrator");
            startActivityForResult(intent, RESULT_ENABLE);
        }
    }

    @SuppressLint("SetTextI18n")
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if (requestCode == RESULT_ENABLE) {
            if (resultCode == Activity.RESULT_OK) {
                //Permission is granted and the app has admin authorization now.
                //LockButton is now visible and the device can be locked and waken up
                lockButton.setVisibility(View.VISIBLE);
            } else {
                //If a problem occurs send a toast with fail message
                Toast.makeText(this, "Failed", Toast.LENGTH_SHORT).show();
            }
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

    public static void turnScreenOnThroughKeyguard(@NonNull Activity activity) {
        // Create a handle and wait for 10 seconds to handle
        final Handler handler = new Handler();
        handler.postDelayed(() -> {
            // Create powerManager and wakelock
            userPowerManagerWakeup(activity);
            // Create flags
            useWindowFlags(activity);
            // Turn the screen on
            useActivityScreenMethods(activity);
        }, 10000); // 10 seconds

    }

    private static void userPowerManagerWakeup(@NonNull Activity activity) {
        PowerManager powerManager = (PowerManager) activity.getSystemService(Context.POWER_SERVICE);
        @SuppressLint("InvalidWakeLockTag") PowerManager.WakeLock wakelock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, " ");
        wakelock.acquire(1);
    }

    @RequiresApi(api = Build.VERSION_CODES.ECLAIR)
    private static void useWindowFlags(@NonNull Activity activity) {
        activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
    }

    private static void useActivityScreenMethods(@NonNull Activity activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
            try {
                activity.setTurnScreenOn(true);
                activity.setShowWhenLocked(true);
            } catch (NoSuchMethodError e) {
                Log.e("enable", "Enable setTurnScreenOn and setShowWhenLocked is not present on device!");
            }
        }
    }

    //TODO: When app is closed by clicking yes in alertDialog,
    // App is deleted from recent apps and restarts with delay.
    // However, it does not restart app if user removes app
    // from recent app without using alert dialog.

    // If user choose yes, kill the app. Otherwise, cancel the dialog and do nothing
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    public void onBackPressed() {
        AlertDialog.Builder builder = new AlertDialog.Builder(Demo.this);
        builder.setTitle(R.string.app_name);
        builder.setIcon(R.mipmap.ic_launcher);
        builder.setMessage("Do you want to exit?")
                .setCancelable(false)
                .setPositiveButton("Yes", (dialog, id) -> finishAndRemoveTask())
                .setNegativeButton("No", (dialog, id) -> dialog.cancel());
        AlertDialog alert = builder.create();
        alert.show();
    }

    @RequiresApi(api = Build.VERSION_CODES.O_MR1)
    @Override
    protected void onResume() {
        isAppClosed = false;
        super.onResume();
        //Revert parameters false in order to turn screen on after each sleep button is clicked
        //activity.setTurnScreenOn(false);
        //activity.setShowWhenLocked(false);
        //getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    }

    @Override
    protected void onStop() {
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        isAppClosed = true;
        Log.d("RestartAppService", "onDestroy MainActivity");
        // runRestartAppService();
        triggerRebirth(this);
        super.onDestroy();
    }

    public static void triggerRebirth(Context context) {
        Intent intent = new Intent(context, Demo.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
        if (context instanceof Activity) {
            ((Activity) context).finish();
        }
        Runtime.getRuntime().exit(0);
    }

    private void runRestartAppService() {
        Log.d("RestartAppService", "openWakeUpService method runs on MainActivity");
        startService(restartAppServiceIntent);
    }
}

NotificationService.java

package com.example.demo.services;

import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.os.Build;
import android.os.SystemClock;
import android.util.Log;

import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;

import com.example.demo.Demo;
import com.example.demo.R;

public class NotificationService extends Service {


    public void onCreate() {
        super.onCreate();
        setNotificationChannel();
    }

    private void setNotificationChannel() {
        if (Build.VERSION.SDK_INT >= 26) {

            // Create a notification channel for android 8+
            String NOTIFICATION_CHANNEL_ID = "example.demo";
            String channelName = "Demo";

            // Initialize the channel
            NotificationChannel notificationChannel = new NotificationChannel
                    (NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_HIGH);
            notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);

            // Create a notification manager and add into notification channel
            NotificationManager notificationManager = (NotificationManager)
                    getSystemService(Context.NOTIFICATION_SERVICE);
            assert notificationManager != null;
            notificationManager.createNotificationChannel(notificationChannel);

            // Create a pending intent for notification to start main activity
            PendingIntent pendingIntent = PendingIntent.getActivity(NotificationService.this,
                    0,
                    new Intent(NotificationService.this, Demo.class),
                    PendingIntent.FLAG_UPDATE_CURRENT);

            // Create the notification's properties
            NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder
                    (this, NOTIFICATION_CHANNEL_ID)
                    .setSmallIcon(R.drawable.ic_launcher_foreground)
                    .setContentTitle("Demo App Notification") // Set its title
                    .setContentText("Application is running on background") // Set its context
                    .setPriority(NotificationManager.IMPORTANCE_HIGH) // Set its priority
                    .setCategory(Notification.CATEGORY_SERVICE) // Set its category
                    .setContentIntent(pendingIntent); // Set its intent when clicked

            Notification notification = notificationBuilder.build(); // Build the notification
            startForeground(1, notification); // Call foreground
        } else {
            stopSelf();
        }
    }


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        Intent restartServiceIntent = new Intent(getApplicationContext(), this.getClass());
        PendingIntent restartServicePendingIntent = PendingIntent.getService
                (getApplicationContext(), 1, restartServiceIntent,
                        PendingIntent.FLAG_ONE_SHOT);
        AlarmManager alarmService = (AlarmManager) getApplicationContext()
                .getSystemService(Context.ALARM_SERVICE);
        alarmService.set
                (AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1,
                        restartServicePendingIntent);
        super.onTaskRemoved(rootIntent);
    }


    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("RestartAppService", "onStart WakeUpService");
        setNotificationChannel();
    }
}

RestartAppService.java

package com.example.demo.services;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;

import com.example.demo.Demo;

public class RestartAppService extends Service {

    @Override
    public void onCreate() {
        Log.d("RestartAppService", "onCreate WakeUpService");
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //Starts main activity after handler's delay
        Log.d("RestartAppService", "onStart WakeUpService");
        if(Demo.isAppClosed){
            restartApp();
        }
        return super.onStartCommand(intent, flags, startId);
    }

    private void restartApp() {
        final Handler handler = new Handler();
        handler.postDelayed(() -> {
            Intent intentMain = new Intent(this, Demo.class);
            intentMain.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            Demo.isAppClosed=false;
            startActivity(intentMain);
        }, 1 * 10000); // 5 minutes timer
    }


    @Override
    public IBinder onBind(Intent intent) {
        Log.d("RestartAppService", "onBind WakeUpService");
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onTaskRemoved(Intent rootIntent) {
 /*
        Log.d("onTaskRemoved", "onTaskRemoved called");
        super.onTaskRemoved(rootIntent);
        // Do
        // Stops foregroundService
        this.stopSelf();
    */
        Log.d("RestartAppService", "onTaskRemoved WakeUpService");
        Intent restartServiceIntent = new Intent(getApplicationContext(), this.getClass());
        PendingIntent restartServicePendingIntent = PendingIntent.getService
                (getApplicationContext(), 1, restartServiceIntent,
                        PendingIntent.FLAG_ONE_SHOT);
        AlarmManager alarmService = (AlarmManager) getApplicationContext()
                .getSystemService(Context.ALARM_SERVICE);
        alarmService.set
                (AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1,
                        restartServicePendingIntent);
        super.onTaskRemoved(rootIntent);
    }

    @Override
    public void onDestroy() {
        Log.d("RestartAppService", "onStart WakeUpService");
        if(Demo.isAppClosed){
            restartApp();
        }
        super.onDestroy();
    }

}

Solution

  • Android 10 (API level 29) and higher place restrictions on when apps can start activities when the app is running in the background.

    Learn more here.