// Copyright 2015 The Project Buendia Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy
// of the License at: http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distrib-
// uted under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
// OR CONDITIONS OF ANY KIND, either express or implied. See the License for
// specific language governing permissions and limitations under the License.
package org.projectbuendia.client.diagnostics;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.support.annotation.Nullable;
import org.projectbuendia.client.utils.Logger;
import java.util.HashSet;
import java.util.Set;
import de.greenrobot.event.EventBus;
/**
* An individual health check to be performed while the application is active.
* <p/>
* <p>Subclasses can choose to implement this class however they see fit so long as they call
* {@link #reportIssue} when a new issue occurs and {@link #resolveIssue} or
* {@link #resolveAllIssues} when issues are resolved. For example, if a health change can be
* triggered by an Android OS broadcast, it may register a {@link BroadcastReceiver}
* in {@link #startImpl}; if it needs to poll another service, it may start a background thread.
* <p/>
* <p>Subclasses must stop checking (e.g., unregister {@link BroadcastReceiver}s or stop
* background threads) when {@link #stopImpl} is called.
*/
public abstract class HealthCheck {
private static final Logger LOG = Logger.create();
private final Object mLock = new Object();
protected final Application mApplication;
protected final HashSet<HealthIssue> mActiveIssues;
@Nullable private EventBus mHealthEventBus;
/**
* Starts the health check.
* <p/>
* <p>After this method is called, the health check must post any health issue events on the
* specified {@link EventBus}.
*/
public final void start(EventBus healthEventBus) {
synchronized (mLock) {
mHealthEventBus = healthEventBus;
startImpl();
}
}
protected abstract void startImpl();
/**
* Stops the health check without clearing its issues.
* <p/>
* <p>{@link #start} may be called again to restart checks.
*/
public final void stop() {
synchronized (mLock) {
mHealthEventBus = null;
stopImpl();
}
}
protected abstract void stopImpl();
/** Clears all the issues for this health check. */
public final void clear() {
mActiveIssues.clear();
}
/**
* Returns true if this HealthCheck knows for certain that the Buendia
* API is unavailable at this moment. Implementations of this method
* should never return true unless they can guarantee that their knowledge
* of the system state is up to date; for example, if a HealthCheck decides
* to return true when the network is down, it is responsible for detecting
* any event that could cause the network to come back up.
*/
public boolean isApiUnavailable() {
return false;
}
protected HealthCheck(Application application) {
mApplication = application;
mActiveIssues = new HashSet<>();
}
/** Reports an issue as being active. */
protected final void reportIssue(HealthIssue healthIssue) {
EventBus eventBus;
synchronized (mLock) {
if (mHealthEventBus == null) {
LOG.w(
"A health issue was reported even though no event bus was registered to "
+ "handle it: %1$s.",
healthIssue.toString());
return;
}
mActiveIssues.add(healthIssue);
eventBus = mHealthEventBus;
}
eventBus.post(healthIssue.discovered);
}
/** Marks as resolved all issues that are currently active. */
protected final void resolveAllIssues() {
EventBus eventBus;
Set<HealthIssue> activeIssues;
synchronized (mLock) {
if (mHealthEventBus == null) {
LOG.w(
"Health issues were resolved even though no event bus was registered to "
+ "handle them.");
return;
}
eventBus = mHealthEventBus;
activeIssues = new HashSet<>(mActiveIssues);
mActiveIssues.clear();
}
for (HealthIssue healthIssue : activeIssues) {
eventBus.post(healthIssue.resolved);
}
}
/**
* Marks as resolved the specified issue.
* <p/>
* <p>If the issue was not previously reported, this method does nothing.
*/
protected final void resolveIssue(HealthIssue healthIssue) {
EventBus eventBus;
boolean wasIssueActive;
synchronized (mLock) {
if (mHealthEventBus == null) {
LOG.w(
"A health issue was resolved even though no event bus was registered to "
+ "handle it: %1$s.",
healthIssue.toString());
return;
}
eventBus = mHealthEventBus;
wasIssueActive = mActiveIssues.remove(healthIssue);
}
if (wasIssueActive) {
eventBus.post(healthIssue.resolved);
}
}
protected final boolean hasIssue(HealthIssue healthIssue) {
for (HealthIssue issue : mActiveIssues) {
if (issue.equals(healthIssue)){
return true;
}
}
return false;
}
}