Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed issue when scan job could restart every time when we have activity navigation #1195

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ dependencies {
androidTestImplementation 'org.apache.commons:commons-math3:3.6.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation "androidx.core:core-ktx:1.12.0"
implementation "androidx.lifecycle:lifecycle-process:2.6.2"

implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
dokkaHtmlPlugin("org.jetbrains.dokka:kotlin-as-java-plugin:1.5.0")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package org.altbeacon.beacon.powersave;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.PowerManager;

import androidx.annotation.NonNull;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ProcessLifecycleOwner;

import org.altbeacon.beacon.BeaconManager;
import org.altbeacon.beacon.logging.LogManager;
Expand All @@ -19,15 +20,14 @@
* @hide internal use only
*/
@TargetApi(18)
public class BackgroundPowerSaverInternal implements Application.ActivityLifecycleCallbacks {
public class BackgroundPowerSaverInternal implements DefaultLifecycleObserver {
@NonNull
private static final String TAG = "BackgroundPowerSaver";
@NonNull
private final BeaconManager beaconManager;
@NonNull
private final Context applicationContext;

private int activeActivityCount = 0;
/**
* Constructs a new BackgroundPowerSaver using the default background determination strategy
*
Expand All @@ -38,68 +38,40 @@ public BackgroundPowerSaverInternal(Context context) {
LogManager.w(TAG, "BackgroundPowerSaver requires API 18 or higher.");
}
applicationContext = context.getApplicationContext();

beaconManager = BeaconManager.getInstanceForApplication(applicationContext);
((Application)applicationContext).registerActivityLifecycleCallbacks(this);
}

@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
}

@Override
public void onActivityStarted(Activity activity) {
}
public void onStart(@NonNull LifecycleOwner owner) {
DefaultLifecycleObserver.super.onStart(owner);

LogManager.d(TAG, "setting foreground mode");

@Override
public void onActivityResumed(Activity activity) {
activeActivityCount++;
if (activeActivityCount < 1) {
LogManager.d(TAG, "reset active activity count on resume. It was %s", activeActivityCount);
activeActivityCount = 1;
}
beaconManager.setBackgroundMode(false);
LogManager.d(TAG, "activity resumed: %s active activities: %s", activity, activeActivityCount);

// If we are in a fallback from a foreground service to the scan jobs due to
// Android restrictions on starting foreground services, this is an opportunity
// to legally start the foreground service now.
BeaconManager.getInstanceForApplication(applicationContext).retryForegroundServiceScanning();

}

@Override
public void onActivityPaused(Activity activity) {
activeActivityCount--;
LogManager.d(
TAG,
"activity paused: %s active activities: %s",
activity,
activeActivityCount
);
if (activeActivityCount < 1) {
LogManager.d(TAG, "setting background mode");
beaconManager.setBackgroundMode(true);
}
}

@Override
public void onActivityStopped(Activity activity) {
}

@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
public void onStop(@NonNull LifecycleOwner owner) {
DefaultLifecycleObserver.super.onStop(owner);

}
LogManager.d(TAG, "setting background mode");

@Override
public void onActivityDestroyed(Activity activity) {
beaconManager.setBackgroundMode(true);
}

/**
* @hide
* Internal library use only
* @hide Internal library use only
* This method will try to infer the initial background state, since it is impossible to know
* if this application has an activity in the foreground from a library unless a lifecycle
* tracker started on Application.onCreate(). Becasue we cannot guarantee that is when we were
* tracker started on Application.onCreate(). Because we cannot guarantee that is when we were
* called
*/
public void enableDefaultBackgroundStateInference() {
Expand All @@ -109,8 +81,7 @@ public void enableDefaultBackgroundStateInference() {
if (methodCalledByApplicationOnCreate()) {
// if we were called by application.onCreate we know we are not yet in the foreground
inferBackground("application.onCreate in the call stack");
}
else {
} else {
PowerManager powerManager = (PowerManager) applicationContext.getSystemService(Context.POWER_SERVICE);
boolean screenOffNow = false;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT_WATCH) {
Expand All @@ -119,8 +90,7 @@ public void enableDefaultBackgroundStateInference() {
if (screenOffNow) {
// if the screen is off, we know we are in the background
inferBackground("the screen being off");
}
else {
} else {
// Register for the screen going off in the future when we still don't know the
// background state. If we see it happen, we know we are in the
// background.
Expand All @@ -133,29 +103,30 @@ public void enableDefaultBackgroundStateInference() {
LogManager.i(TAG, "Background mode not set. We assume we are in the foreground.");
}
}

private void inferBackground(String inferenceMechanism) {
if (beaconManager.isBackgroundModeUninitialized()) {
LogManager.i(TAG, "We have inferred by "+inferenceMechanism+" that we are in the background.");
LogManager.i(TAG, "We have inferred by " + inferenceMechanism + " that we are in the background.");
beaconManager.setBackgroundModeInternal(true);
}
}

// Sadly, Android APIs give us no way of knowing if Application.onCreate has completed or if
// we are otherwise in a launching state prior to when any UI components have been displayed
// So we do a trick here that approximates this -- we check if the stack trace has this method
// inside of it. This will work if the call stack that initiates this direcdtly comes from
// Application.onCreate, but it will fail to detect any asynchronous executions that start there,
// but managed to compelete executing before UI elements were launched.
// but managed to complete executing before UI elements were launched.
private boolean methodCalledByApplicationOnCreate() {
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
String targetMethodname = "onCreate";
String targetMethodName = "onCreate";
String targetClassname = Application.class.getCanonicalName();
android.app.Instrumentation a;
for (StackTraceElement element: stackTraceElements) {
if (targetMethodname.equals(element.getMethodName())) {
for (StackTraceElement element : stackTraceElements) {
if (targetMethodName.equals(element.getMethodName())) {
if (targetClassname.equals(element.getClassName())) {
return true;
}
else {
} else {
// See if the target method is a superclass of the actual calling method
if (element.getClassName() != null) {
try {
Expand All @@ -167,14 +138,16 @@ private boolean methodCalledByApplicationOnCreate() {
}
}

} catch (ClassNotFoundException e) { }
} catch (ClassNotFoundException e) {
}
}
}
}
}
return false;
}
private BroadcastReceiver screenOffReceiver = new BroadcastReceiver() {

private final BroadcastReceiver screenOffReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
inferBackground("the screen going off");
Expand Down