/* * Copyright (C) 2014 jonas.oreland@gmail.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.runnerup.tracker.component; import android.annotation.TargetApi; import android.content.Context; import android.os.Build; import android.os.Handler; import android.util.Log; import android.util.Pair; import org.runnerup.BuildConfig; import java.util.HashMap; /** * Created by jonas on 12/11/14. * * Class for managing a set of TrackerComponents as if they were one */ @TargetApi(Build.VERSION_CODES.FROYO) public class TrackerComponentCollection implements TrackerComponent { final Handler handler = new Handler(); final HashMap<String, Pair<TrackerComponent, ResultCode>> components = new HashMap<String, Pair<TrackerComponent, ResultCode>>(); final HashMap<String, TrackerComponent> pending = new HashMap<String, TrackerComponent>(); public TrackerComponent addComponent(TrackerComponent component) { components.put(component.getName(), new Pair<TrackerComponent, ResultCode>(component, ResultCode.RESULT_OK)); return component; } public TrackerComponent getComponent(String key) { synchronized (components) { if (components.containsKey(key)) return components.get(key).first; else if (pending.containsKey(key)) return pending.get(key); return null; } } public ResultCode getResultCode(String key) { synchronized (components) { if (components.containsKey(key)) return components.get(key).second; else if (pending.containsKey(key)) return ResultCode.RESULT_PENDING; return ResultCode.RESULT_ERROR; } } @Override public String getName() { return "TrackerComponentCollection"; } @Override public boolean isConnected() { return true; } /** * Called by Tracker during initialization */ @Override public ResultCode onInit(final Callback callback, Context context) { return forEach("onInit", new Func1() { @Override public ResultCode apply(TrackerComponent comp0, ResultCode currentResultCode, Callback callback0, Context context0) { if (currentResultCode == ResultCode.RESULT_OK) return comp0.onInit(callback0, context0); else return currentResultCode; } }, callback, context); } @Override public ResultCode onConnecting(final Callback callback, Context context) { return forEach("onConnecting", new Func1() { @Override public ResultCode apply(TrackerComponent comp0, ResultCode currentResultCode, Callback callback0, Context context0) { if (currentResultCode == ResultCode.RESULT_OK || currentResultCode == ResultCode.RESULT_UNKNOWN) return comp0.onConnecting(callback0, context0); else return currentResultCode; } }, callback, context); } @Override public void onConnected() { for (Pair<TrackerComponent, ResultCode> pair : components.values()) { if (pair.second == ResultCode.RESULT_OK) { pair.first.onConnected(); } } } private ResultCode getResult(HashMap<String, Pair<TrackerComponent, ResultCode>> components) { ResultCode res = ResultCode.RESULT_OK; for (Pair<TrackerComponent,ResultCode> pair : components.values()) { if (pair.second == ResultCode.RESULT_ERROR_FATAL) { // can't get any worse than this return ResultCode.RESULT_ERROR_FATAL; } else if (pair.second == ResultCode.RESULT_ERROR) { res = ResultCode.RESULT_ERROR; } } return res; } /** * Called by Tracker before start * Component shall populate bindValues * with objects that will then be passed * to workout */ @Override public void onBind(HashMap<String, Object> bindValues) { for (Pair<TrackerComponent, ResultCode> pair : components.values()) { if (pair.second == ResultCode.RESULT_OK) { pair.first.onBind(bindValues); } } } /** * Called by Tracker when workout starts */ @Override public void onStart() { for (Pair<TrackerComponent, ResultCode> pair : components.values()) { if (pair.second == ResultCode.RESULT_OK) { pair.first.onStart(); } } } /** * Called by Tracker when workout is paused */ @Override public void onPause() { for (Pair<TrackerComponent, ResultCode> pair : components.values()) { if (pair.second == ResultCode.RESULT_OK) { pair.first.onPause(); } } } /** * Called by Tracker when workout is resumed */ @Override public void onResume() { for (Pair<TrackerComponent, ResultCode> pair : components.values()) { if (pair.second == ResultCode.RESULT_OK) { pair.first.onResume(); } } } /** * Called by Tracker when workout is complete */ @Override public void onComplete(boolean discarded) { for (Pair<TrackerComponent, ResultCode> pair : components.values()) { if (pair.second == ResultCode.RESULT_OK) { pair.first.onComplete(discarded); } } } /** * Called by tracked after workout has ended */ @Override public ResultCode onEnd(final Callback callback, Context context) { return forEach("onEnd", new Func1() { @Override public ResultCode apply(TrackerComponent comp0, ResultCode currentResultCode, Callback callback0, Context context0) { // Ignore current result code, always run onEnd() return comp0.onEnd(callback0, context0); } }, callback, context); } private interface Func1 { ResultCode apply(TrackerComponent component, ResultCode currentResultCode, Callback callback, Context context); } private ResultCode forEach(final String msg, final Func1 func, final Callback callback, Context context) { synchronized (components) { HashMap<String, Pair<TrackerComponent, ResultCode>> list = new HashMap<String, Pair<TrackerComponent, ResultCode>>(); list.putAll(components); components.clear(); for (TrackerComponent component : pending.values()) { list.put(component.getName(), new Pair<TrackerComponent, ResultCode>(component, ResultCode.RESULT_PENDING)); } pending.clear(); for (final String key : list.keySet()) { final Pair<TrackerComponent, ResultCode> p = list.get(key); final TrackerComponent component = p.first; final ResultCode currentResultCode = p.second; ResultCode res = func.apply(component, currentResultCode, new Callback() { @Override public void run(final TrackerComponent component, final ResultCode resultCode) { handler.post(new Runnable() { @Override public void run() { synchronized (components) { Log.e(getName(), component.getName() + " " + msg + " => " + resultCode); if (!pending.containsKey(key)) return; TrackerComponent check = pending.remove(key); if (BuildConfig.DEBUG && check != component){ Log.e(getName(), component.getName() + " != " + check.getName()); throw new AssertionError(); } components.put(key, new Pair<TrackerComponent, ResultCode>( component, resultCode)); if (pending.isEmpty()) { Log.e(getName(), " => runCallback()"); callback.run(TrackerComponentCollection.this, getResult(components)); } } } }); } }, context); Log.e(getName(), component.getName() + " " + msg + " => " + res); if (res != ResultCode.RESULT_PENDING) { components.put(key, new Pair<TrackerComponent, ResultCode>(component, res)); } else { pending.put(key, component); } } } if (!pending.isEmpty()) return ResultCode.RESULT_PENDING; else { Log.e(getName(), " => return directly"); return getResult(components); } } }