5

I know this's a repeated question but i've looked all over the place and couldn't find a solution that works for me so...

I've an app that fetches movie data from TMDB API, it fetches it by page using a sync adapter, basically it runs great except when the sync adapter's periodic sync is ran while the app is open and the user is not at the first page, so my options were to force update the movies list and send the user to the top of the list which is totally bad experience so not considered as an option, Or i could check if the app is running, either in foreground or in the app stack that it's not visible to the user yet still running, so the best i could get to by searching was this piece of code:

   public static boolean isAppRunning(final Context context, final String packageName) {

    final ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    final List<ActivityManager.RunningAppProcessInfo> procInfos = activityManager.getRunningAppProcesses();

    if (procInfos != null) {
        for (final ActivityManager.RunningAppProcessInfo processInfo : procInfos) {
            if (processInfo.processName.equals(packageName))
                return true;
        }
    }

    return false;
}

but for whatever reason it doesn't work correctly and sees the app as running even if i killed it by swiping, at first i thought it was because i have a sync adapter which could be considered as the app running so i used another process for it in the manifest using the

android:process=":service"

but it didn't work

Also thought about using variables in onStart and onStop of the activity but it's not guaranteed that onStop will be called when app is closed or killed so trying to avoid this method

this's my manifest.xml

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="inc.mourad.ahmed.watchme">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />

<permission
    android:name="inc.ahmed.mourad.ACCESS_WATCHME_DATABASE"
    android:label="access my movies content provider"
    android:protectionLevel="dangerous" />
<permission
    android:name="inc.ahmed.mourad.ACCESS_WATCHME_SYNC_SERVICE"
    android:label="access my sync service"
    android:protectionLevel="dangerous" />
<permission
    android:name="inc.ahmed.mourad.ACCESS_WATCHME_SYNC_AUTHENTICATOR_SERVICE"
    android:label="access my sync authenticator dummy service"
    android:protectionLevel="dangerous" />

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/Theme.AppCompat">

    <activity android:name=".SplashScreenActivity"
        android:theme="@style/Theme.AppCompat.NoActionBar">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <activity
        android:name=".MainActivity"
        android:label="@string/app_name"
        android:theme="@style/Theme.AppCompat">
        <intent-filter>
            <data
                android:host="www.ahmedmourad.com"
                android:pathPrefix="/watchme/"
                android:scheme="http" />

            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT"/>
            <category android:name="android.intent.category.BROWSABLE"/>
        </intent-filter>
    </activity>

    <activity
        android:name=".SettingsActivity"
        android:label="@string/title_activity_settings"
        android:parentActivityName=".MainActivity">
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value="inc.mourad.ahmed.watchme.MainActivity" />
    </activity>
    <activity android:name=".MovieDetailsActivity" />

    <provider
        android:name=".data.MovieProvider"
        android:authorities="@string/content_authority"
        android:enabled="true"
        android:exported="false"
        android:permission="inc.ahmed.mourad.ACCESS_WATCHME_DATABASE"
        android:syncable="true" />

    <!-- SyncAdapter's dummy authentication service -->
    <service android:name=".sync.WatchMeAuthenticatorService"
        android:permission="inc.ahmed.mourad.ACCESS_WATCHME_SYNC_AUTHENTICATOR_SERVICE"
        android:process=":service">
        <intent-filter>
            <action android:name="android.accounts.AccountAuthenticator" />
        </intent-filter>

        <meta-data
            android:name="android.accounts.AccountAuthenticator"
            android:resource="@xml/authenticator" />
    </service>

    <!-- The SyncAdapter service -->
    <service
        android:name=".sync.WatchMeSyncService"
        android:exported="true"
        android:permission="inc.ahmed.mourad.ACCESS_WATCHME_SYNC_SERVICE"
        android:process=":service">
        <intent-filter>
            <action android:name="android.content.SyncAdapter" />
        </intent-filter>

        <meta-data
            android:name="android.content.SyncAdapter"
            android:resource="@xml/syncadapter" />
    </service>

</application>

so, any ideas? Thanks in advance

Ahmed Mourad
  • 193
  • 1
  • 2
  • 18
  • onStop is guaranteed to be called on API 11+ , isn`t this enough for you? – X3Btel Apr 12 '17 at 20:35
  • @X3Btel it says [here](http://stackoverflow.com/questions/29390760/is-fragment-onstop-guaranteed-to-be-called) it might not be called on low memory situations so just looking for a safer solution if not, will just go with it and try to decrease the loss – Ahmed Mourad Apr 12 '17 at 21:06
  • This is for fragments, for activity is guaranteed. You simply need to imlement your foreground callbacks in the activity not fragment http://stackoverflow.com/questions/29395169/is-activity-onstop-guaranteed-to-be-called-api-11 – X3Btel Apr 12 '17 at 21:37
  • I used this answer http://stackoverflow.com/a/19920353/4810277 It works well, only problem as stated is if you lock/unlock the phone, but should work in your case as well – X3Btel Apr 12 '17 at 21:39
  • @X3Btel missed that point, ok will do, thanks for help – Ahmed Mourad Apr 12 '17 at 21:41
  • thanks for the other answer too, will definitely need it here – Ahmed Mourad Apr 12 '17 at 22:11
  • Possible duplicate of [How to detect when an Android app goes to the background and come back to the foreground](http://stackoverflow.com/questions/4414171/how-to-detect-when-an-android-app-goes-to-the-background-and-come-back-to-the-fo) – X3Btel Apr 13 '17 at 07:27

2 Answers2

16

We tweaked your idea and came up with this (in Kotlin, see below for Java):

fun appInForeground(context: Context): Boolean {
    val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    val runningAppProcesses = activityManager.runningAppProcesses ?: return false
    return runningAppProcesses.any { it.processName == context.packageName && it.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND }
}

This is the equivalent in Java:

private boolean appInForeground(@NonNull Context context) {
    ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    List<ActivityManager.RunningAppProcessInfo> runningAppProcesses = activityManager.getRunningAppProcesses();
    if (runningAppProcesses == null) {
        return false;
    }

    for (ActivityManager.RunningAppProcessInfo runningAppProcess : runningAppProcesses) {
        if (runningAppProcess.processName.equals(context.getPackageName()) &&
                runningAppProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
            return true;
        }
    }
    return false;
}

Here's the relevant documentation.

mtrewartha
  • 761
  • 7
  • 18
5

Original Answer : https://stackoverflow.com/a/48767617/10004454 The recommended way to do it in accordance with Android documentation is

class MyApplication : Application(), LifecycleObserver {

override fun onCreate() {
    super.onCreate()
    ProcessLifecycleOwner.get().lifecycle.addObserver(this)
}

fun isInForeground(): Boolean {
    return ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)
}

@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onAppBackgrounded() {
    //App in background

    Log.e(TAG, "************* backgrounded")
    Log.e(TAG, "************* ${isInForeground()}")
}

@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onAppForegrounded() {

    Log.e(TAG, "************* foregrounded")
    Log.e(TAG, "************* ${isInForeground()}")
    // App in foreground
}}

In your gradle (app) add : implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"

Then to check the state at runtime call (applicationContext as MyApplication).isActivityVisible()

Yonko Kilasi
  • 337
  • 3
  • 9
  • But I think this is not a proper solution yet. because, every time I navigate to another activity `onAppBackgrounded()` is calling every time for the current activity and get the `onAppForegrounded()` for the next activity I am navigating to. – A S M Sayem Mar 04 '20 at 06:42
  • I am unable to replicate this , Have you only registered the lifecycle observer in the application class?.Because onAppBackgrounded should only be called when the Applications lifecycle is stopped (@OnLifecycleEvent(Lifecycle.Event.ON_STOP)) . You can read more about it here in the android [documentation](https://developer.android.com/reference/androidx/lifecycle/Lifecycle) @Saadat – Yonko Kilasi Mar 05 '20 at 08:00