/* SpheroManager.java Copyright (c) 2014 NTT DOCOMO,INC. Released under the MIT license http://opensource.org/licenses/mit-license.php */ package org.deviceconnect.android.deviceplugin.sphero; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.util.Log; import com.orbotix.ConvenienceRobot; import com.orbotix.DualStackDiscoveryAgent; import com.orbotix.async.CollisionDetectedAsyncData; import com.orbotix.async.DeviceSensorAsyncMessage; import com.orbotix.common.DiscoveryException; import com.orbotix.common.Robot; import com.orbotix.common.RobotChangedStateListener; import com.orbotix.common.sensor.Acceleration; import com.orbotix.common.sensor.GyroData; import com.orbotix.common.sensor.LocatorData; import com.orbotix.common.sensor.QuaternionSensor; import com.orbotix.common.sensor.ThreeAxisSensor; import com.orbotix.macro.MacroObject; import com.orbotix.macro.cmd.BackLED; import com.orbotix.macro.cmd.Delay; import com.orbotix.macro.cmd.RGB; import org.deviceconnect.android.deviceplugin.sphero.data.DeviceInfo; import org.deviceconnect.android.deviceplugin.sphero.profile.SpheroLightProfile; import org.deviceconnect.android.deviceplugin.sphero.profile.SpheroProfile; import org.deviceconnect.android.event.Event; import org.deviceconnect.android.event.EventManager; import org.deviceconnect.android.profile.DeviceOrientationProfile; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; /** * Spheroの操作機能を提供するクラス. * @author NTT DOCOMO, INC. */ public final class SpheroManager implements DeviceInfo.DeviceSensorListener, DeviceInfo.DeviceCollisionListener { /** * シングルトンなManagerのインスタンス. */ public static final SpheroManager INSTANCE = new SpheroManager(); /** * 接続のタイムアウト. */ private static final int CONNECTION_TIMEOUT = 30000; /** * 切断のリトライ回数. */ private static final int DISCONNECTION_RETRY_NUM = 50; /** * 切断のリトライ遅延. */ private static final int DISCONNECTION_RETRY_DELAY = 1000; /** * Spheroを見失った際のリトライ回数. */ private static final int SEARCH_RETRY_NUM = 10; /** * 1G = {@value} . */ private static final double G = 9.81; /** * ScheduledExecutorServiceインスタンス. */ private ScheduledExecutorService mExecutor = Executors.newSingleThreadScheduledExecutor(); /** * Spheroが見つからなくなったことを検知するためのタイマー. */ private ScheduledFuture<?> mScanTimerFuture; /** * 初回実行時間. */ private static final long SCAN_FIRST_WAIT_PERIOD = 1000; /** * 次回実行時. */ private static final long SCAN_WAIT_PERIOD = 5 * 1000; /** * 検知したデバイス一覧. */ private ConcurrentHashMap<String, DeviceInfo> mDevices; /** * 検知中フラグ. */ private boolean mIsDiscovering; /** * 検知されたデバイスの一覧. まだ未接続で、検知されただけの状態の一覧. */ private List<Robot> mFoundDevices = Collections.synchronizedList(new ArrayList<Robot>()); /** * 見つからなくなったデバイスを管理するオブジェクト. */ private Map<String, Integer> mCounting = new HashMap<String, Integer>(); /** * デバイス検知リスナー. */ private DeviceDiscoveryListener mDiscoveryListener; /** * 接続のロック. */ private Object mConnLock; /** * サービス. */ private SpheroDeviceService mService; /** * 一時的にDeviceInfoをキャッシュする変数. */ private DeviceInfo mCacheDeviceInfo; /** * 一時的にDeviceSensorsDataをキャッシュする変数. */ private DeviceSensorAsyncMessage mCacheDeviceSensorsData; /** * Discovery Listener. */ private DiscoveryListenerImpl mDiscoveryListenerImpl; /** * 一時的にインターバルをキャッシュする変数. */ private long mCacheInterval; /** * デバイス検知の通知を受けるリスナー. */ public interface DeviceDiscoveryListener { /** * 見つかったデバイスを通知します. * * @param sphero 見つかったデバイス */ void onDeviceFound(ConvenienceRobot sphero); /** * 消失したデバイスの通知を受けるリスナー. * * @param sphero 消失したデバイス */ void onDeviceLost(ConvenienceRobot sphero); /** * すべてのデバイスの消失を通知します. */ void onDeviceLostAll(); } /** * Spheroを操作するためのオブジェクト */ private DualStackDiscoveryAgent discoveryAgent; /** * タイムアウト. */ private int mConnectingTimeoutCount; private boolean mScanning = false; /** * 接続中のフラグリスト. */ private ConcurrentMap<String, Boolean> mConnectingFlags; /** * SpheroManagerを生成する. */ private SpheroManager() { mDevices = new ConcurrentHashMap<String, DeviceInfo>(); discoveryAgent = DualStackDiscoveryAgent.getInstance(); discoveryAgent.setMaxConnectedRobots(4); mDiscoveryListenerImpl = new DiscoveryListenerImpl(); discoveryAgent.addRobotStateListener(mDiscoveryListenerImpl); mConnLock = new Object(); mConnectingTimeoutCount = 0; mConnectingFlags = new ConcurrentHashMap<String, Boolean>(); } /** * 検知を開始する. * * @param context コンテキストオブジェクト. */ public synchronized void startDiscovery(final Context context) { if (mIsDiscovering) { return; } if (BuildConfig.DEBUG) { Log.d("TEST", "start discovery"); } try { mIsDiscovering = discoveryAgent.startDiscovery(context); scanSphero(true); } catch (DiscoveryException e) { mIsDiscovering = false; } } /** * デバイス検知のリスナーを設定する. * * @param listener リスナー */ public synchronized void setDiscoveryListener(final DeviceDiscoveryListener listener) { mDiscoveryListener = listener; } /** * 検知を終了する. */ public synchronized void stopDiscovery() { if (!mIsDiscovering) { return; } if (BuildConfig.DEBUG) { Log.d("TEST", "stop discovery"); } if (discoveryAgent.isDiscovering()) { discoveryAgent.stopDiscovery(); } mIsDiscovering = false; // if (mFoundDevices != null) { // mFoundDevices.clear(); // } } /** * Spheroの操作を全てシャットダウンさせる. */ public synchronized void shutdown() { stopDiscovery(); discoveryAgent.removeRobotStateListener(mDiscoveryListenerImpl); mDiscoveryListenerImpl = null; discoveryAgent.disconnectAll(); mService = null; } /** * 検知したデバイスの一覧を取得する. * * @return デバイス一覧 */ public synchronized List<Robot> getFoundDevices() { return mFoundDevices; } /** * 接続済みのデバイス一覧を取得する. * * @return 接続済みのデバイス一覧 */ public synchronized Collection<DeviceInfo> getConnectedDevices() { return mDevices.values(); } /** * 指定されたサービスIDを持つデバイスを取得する. * * @param serviceId サービスID * @return デバイス。無い場合はnullを返す。 */ public DeviceInfo getDevice(final String serviceId) { return mDevices.get(serviceId); } /** * 未接続の端末一覧から一致するものを取得する. * * @param serviceId サービスID * @return デバイス。無い場合はnull。 */ public synchronized Robot getNotConnectedDevice(final String serviceId) { if (mFoundDevices == null) { return null; } for (Robot s : mFoundDevices) { if (s.getIdentifier().equals(serviceId)) { return s; } } return null; } /** * 未接続の端末一覧から一致するものを削除する. * * @param serviceId サービスID * @return デバイス。無い場合はnull。 */ public synchronized Robot removeNotConnectedDevice(final String serviceId) { if (mFoundDevices == null) { return null; } for (int i = 0; i < mFoundDevices.size(); i++) { if (mFoundDevices.get(i).getIdentifier().equals(serviceId)) { mCounting.remove(serviceId); mDevices.remove(serviceId); return mFoundDevices.remove(i); } } return null; } /** * 指定されたIDのSpheroを接続解除する. * * @param id SpheroのUUID */ public void disconnect(final String id) { if (id == null) { return; } DeviceInfo removed = mDevices.remove(id); if (removed != null) { final ConvenienceRobot sphero = removed.getDevice(); for (int i = 0; i < DISCONNECTION_RETRY_NUM; i++) { if (!sphero.isConnected()) { break; } sphero.disconnect(); try { Thread.sleep(DISCONNECTION_RETRY_DELAY); } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * 指定されたIDを持つSpheroに接続する. * * @param id SpheroのUUID * @return 成功の場合 true、失敗ならfalseを返す。 */ public boolean connect(final String id) { Robot connected = null; synchronized (this) { if (mFoundDevices == null) { return false; } for (Robot s : mFoundDevices) { if (s.getIdentifier().equals(id)) { if (s.isOnline()) { return true; } connected = s; break; } } } ConvenienceRobot cRobot = null; if (connected != null) { mConnectingFlags.put(connected.getIdentifier(), true); mConnectingTimeoutCount = 0; discoveryAgent.connect(connected); do { try { Thread.sleep(DISCONNECTION_RETRY_DELAY); } catch (InterruptedException e) { continue; } mConnectingTimeoutCount++; DeviceInfo info = mDevices.get(connected.getIdentifier()); if (info != null) { cRobot = info.getDevice(); if (BuildConfig.DEBUG) { Log.d("TEST", "retryConnecting:" + cRobot.isConnected()); } break; } else { if (BuildConfig.DEBUG) { Log.d("TEST", "retryConnecting......"); } } // Time Out } while (mConnectingTimeoutCount < DISCONNECTION_RETRY_NUM); } return (cRobot != null); } /** * 指定されたデバイスのセンサーを1回だけ監視する. * * @param device デバイス * @param listener 監視結果を通知するリスナー */ public void startSensor(final DeviceInfo device, final DeviceInfo.DeviceSensorListener listener) { synchronized (device) { if (!device.isSensorStarted()) { device.startSensor(new DeviceInfo.DeviceSensorListener() { @Override public void sensorUpdated(final DeviceInfo info, final DeviceSensorAsyncMessage data, final long interval) { if (listener != null) { listener.sensorUpdated(info, data, interval); } if (!hasSensorListener(device.getDevice().getRobot().getIdentifier())) { stopSensor(device); } } }); } else { if (listener != null) { listener.sensorUpdated(mCacheDeviceInfo, mCacheDeviceSensorsData, mCacheInterval); } } } } /** * 指定されたデバイスのセンサー監視を開始する. * * @param device デバイス */ public void startSensor(final DeviceInfo device) { synchronized (device) { if (!device.isSensorStarted()) { device.startSensor(this); } } } /** * 指定されたデバイスのセンサー監視を停止する. * * @param device デバイス */ public void stopSensor(final DeviceInfo device) { synchronized (device) { if (device.isSensorStarted()) { device.stopSensor(); } } } /** * 指定されたデバイスの衝突監視を開始する. * * @param device デバイス */ public void startCollision(final DeviceInfo device) { synchronized (device) { if (!device.isCollisionStarted()) { device.startCollistion(this); } } } /** * 指定されたデバイスの衝突監視を開始する. * * @param device デバイス * @param listener リスナー */ public void startCollision(final DeviceInfo device, final DeviceInfo.DeviceCollisionListener listener) { synchronized (device) { if (!device.isCollisionStarted()) { device.startCollistion(new DeviceInfo.DeviceCollisionListener() { @Override public void collisionDetected(final DeviceInfo info, final CollisionDetectedAsyncData data) { if (listener != null) { listener.collisionDetected(info, data); } if (!hasCollisionListener(device.getDevice().getRobot().getIdentifier())) { stopCollision(device); } } }); } } } /** * 指定されたデバイスの衝突監視を停止する. * * @param device デバイス */ public void stopCollision(final DeviceInfo device) { synchronized (device) { if (device.isCollisionStarted()) { device.stopCollision(); } } } /** * サービスを設定する. * * @param service サービス */ public void setService(final SpheroDeviceService service) { mService = service; } /** * センサー系のイベントを持っているかチェックする. * * @param info デバイス * @return 持っているならtrue、その他はfalseを返す。 */ public boolean hasSensorEvent(final DeviceInfo info) { List<Event> eventQua = EventManager.INSTANCE.getEventList(info.getDevice().getRobot().getIdentifier(), SpheroProfile.PROFILE_NAME, SpheroProfile.INTER_QUATERNION, SpheroProfile.ATTR_ON_QUATERNION); List<Event> eventOri = EventManager.INSTANCE.getEventList(info.getDevice().getRobot().getIdentifier(), DeviceOrientationProfile.PROFILE_NAME, null, DeviceOrientationProfile.ATTRIBUTE_ON_DEVICE_ORIENTATION); List<Event> eventLoc = EventManager.INSTANCE.getEventList(info.getDevice().getRobot().getIdentifier(), SpheroProfile.PROFILE_NAME, SpheroProfile.INTER_LOCATOR, SpheroProfile.ATTR_ON_LOCATOR); return (eventOri.size() != 0) || (eventQua.size() != 0) || (eventLoc.size() != 0); } /** * バックライトを点滅させる. * * @param info デバイス情報 * @param intensity 明るさ * @param pattern パターン */ public static void flashBackLight(final DeviceInfo info, final int intensity, final long[] pattern) { MacroObject m = new MacroObject(); for (int i = 0; i < pattern.length; i++) { if (i % 2 == 0) { m.addCommand(new BackLED(intensity, 0)); } else { m.addCommand(new BackLED(0, 0)); } m.addCommand(new Delay((int) pattern[i])); } int oriIntensity = (int) (info.getBackBrightness() * SpheroLightProfile.MAX_BRIGHTNESS); m.addCommand(new BackLED(oriIntensity, 0)); info.getDevice().playMacro(m); } /** * フロントライトを点滅させる. * * @param info デバイス情報 * @param colors 色 * @param pattern パターン */ public static void flashFrontLight(final DeviceInfo info, final int[] colors, final long[] pattern) { MacroObject m = new MacroObject(); for (int i = 0; i < pattern.length; i++) { if (i % 2 == 0) { m.addCommand(new RGB(colors[0], colors[1], colors[2], 0)); } else { m.addCommand(new RGB(0, 0, 0, 0)); } m.addCommand(new Delay((int) pattern[i])); } info.getDevice().playMacro(m); } /** * 指定されたデータからOrientationデータを作成する. * * @param data データ * @param interval インターバル * @return Orientationデータ */ public static Bundle createOrientation(final DeviceSensorAsyncMessage data, final long interval) { Acceleration accData = data.getAsyncData().get(0).getAccelerometerData().getFilteredAcceleration(); Bundle accelerationIncludingGravity = new Bundle(); // Spheroでは単位がG(1G=9.81m/s^2)で正規化しているので、Device Connectの単位(m/s^2)に変換する。 DeviceOrientationProfile.setX(accelerationIncludingGravity, accData.x * G); DeviceOrientationProfile.setY(accelerationIncludingGravity, accData.y * G); DeviceOrientationProfile.setZ(accelerationIncludingGravity, accData.z * G); GyroData gyroData = data.getAsyncData().get(0).getGyroData(); ThreeAxisSensor threeAxisSensor = gyroData.getRotationRateFiltered(); Bundle rotationRate = new Bundle(); DeviceOrientationProfile.setAlpha(rotationRate, 0.1d * threeAxisSensor.x); DeviceOrientationProfile.setBeta(rotationRate, 0.1d * threeAxisSensor.y); DeviceOrientationProfile.setGamma(rotationRate, 0.1d * threeAxisSensor.z); Bundle orientation = new Bundle(); DeviceOrientationProfile.setAccelerationIncludingGravity(orientation, accelerationIncludingGravity); DeviceOrientationProfile.setRotationRate(orientation, rotationRate); DeviceOrientationProfile.setInterval(orientation, interval); return orientation; } /** * 指定されたデータからQuaternionデータを作成する. * * @param data データ * @param interval インターバル * @return Quaternionデータ */ public static Bundle createQuaternion(final DeviceSensorAsyncMessage data, final long interval) { QuaternionSensor quat = data.getAsyncData().get(0).getQuaternion(); Bundle quaternion = new Bundle(); quaternion.putDouble(SpheroProfile.PARAM_Q0, quat.q0); quaternion.putDouble(SpheroProfile.PARAM_Q1, quat.q1); quaternion.putDouble(SpheroProfile.PARAM_Q2, quat.q2); quaternion.putDouble(SpheroProfile.PARAM_Q3, quat.q3); quaternion.putLong(SpheroProfile.PARAM_INTERVAL, interval); return quaternion; } /** * 指定されたデータからLocatorデータを作成する. * * @param data データ * @return Locatorデータ */ public static Bundle createLocator(final DeviceSensorAsyncMessage data) { LocatorData loc = data.getAsyncData().get(0).getLocatorData(); Bundle locator = new Bundle(); locator.putFloat(SpheroProfile.PARAM_POSITION_X, loc.getPositionX()); locator.putFloat(SpheroProfile.PARAM_POSITION_Y, loc.getPositionY()); locator.putFloat(SpheroProfile.PARAM_VELOCITY_X, loc.getVelocityX()); locator.putFloat(SpheroProfile.PARAM_VELOCITY_Y, loc.getVelocityY()); return locator; } /** * 指定されたデータからCollisionデータを作成する. * * @param data データ * @return Collisionデータ */ public static Bundle createCollision(final CollisionDetectedAsyncData data) { Bundle collision = new Bundle(); Acceleration impactAccelerationData = data.getImpactAcceleration(); Bundle impactAcceleration = new Bundle(); impactAcceleration.putDouble(SpheroProfile.PARAM_X, impactAccelerationData.x); impactAcceleration.putDouble(SpheroProfile.PARAM_Y, impactAccelerationData.y); impactAcceleration.putDouble(SpheroProfile.PARAM_Z, impactAccelerationData.z); Bundle impactAxis = new Bundle(); impactAxis.putBoolean(SpheroProfile.PARAM_X, data.hasImpactXAxis()); impactAxis.putBoolean(SpheroProfile.PARAM_Y, data.hasImpactYAxis()); CollisionDetectedAsyncData.CollisionPower power = data.getImpactPower(); Bundle impactPower = new Bundle(); impactPower.putShort(SpheroProfile.PARAM_X, power.x); impactPower.putShort(SpheroProfile.PARAM_Y, power.y); collision.putBundle(SpheroProfile.PARAM_IMPACT_ACCELERATION, impactAcceleration); collision.putBundle(SpheroProfile.PARAM_IMPACT_AXIS, impactAxis); collision.putBundle(SpheroProfile.PARAM_IMPACT_POWER, impactPower); collision.putFloat(SpheroProfile.PARAM_IMPACT_SPEED, data.getImpactSpeed()); collision.putLong(SpheroProfile.PARAM_IMPACT_TIMESTAMP, data.getTimeStamp().getTime()); return collision; } /** * Spheroが接続された時の処理. * * @param sphero 接続されたSphero */ private void onConnected(final ConvenienceRobot sphero) { DeviceInfo info = new DeviceInfo(); info.setDevice(sphero); info.setBackBrightness(1.f); sphero.enableStabilization(true); if (BuildConfig.DEBUG) { Log.d("TEST", "connected device : " + sphero.getRobot().getIdentifier()); } if (!mDevices.contains(sphero.getRobot().getIdentifier())) { mDevices.put(sphero.getRobot().getIdentifier(), info); } } /** * センサーのイベントが登録されているか確認する. * * @param serviceId サービスID * @return 登録されている場合はtrue、それ以外はfalse */ private boolean hasSensorListener(final String serviceId) { List<Event> events = EventManager.INSTANCE.getEventList(serviceId, DeviceOrientationProfile.PROFILE_NAME, null, DeviceOrientationProfile.ATTRIBUTE_ON_DEVICE_ORIENTATION); if (events != null && events.size() > 0) { return true; } events = EventManager.INSTANCE.getEventList(serviceId, SpheroProfile.PROFILE_NAME, SpheroProfile.INTER_QUATERNION, SpheroProfile.ATTR_ON_QUATERNION); if (events != null && events.size() > 0) { return true; } events = EventManager.INSTANCE.getEventList(serviceId, SpheroProfile.PROFILE_NAME, SpheroProfile.INTER_LOCATOR, SpheroProfile.ATTR_ON_LOCATOR); return (events != null && events.size() > 0); } /** * 衝突のイベントが登録されているか確認する. * * @param serviceId サービスID * @return 登録されている場合はtrue、それ以外はfalse */ private boolean hasCollisionListener(final String serviceId) { List<Event> events = EventManager.INSTANCE.getEventList(serviceId, SpheroProfile.PROFILE_NAME, SpheroProfile.INTER_COLLISION, SpheroProfile.ATTR_ON_COLLISION); return events != null && events.size() > 0; } /** * 検知リスナー. */ private class DiscoveryListenerImpl implements RobotChangedStateListener { @Override public void handleRobotChangedState(final Robot robot, final RobotChangedStateNotificationType robotChangedStateNotificationType) { ConvenienceRobot cRobot = new ConvenienceRobot(robot); switch (robotChangedStateNotificationType) { case Online: if (mConnectingFlags.get(cRobot.getRobot().getIdentifier()) == null || mConnectingFlags.get(cRobot.getRobot().getIdentifier()) != null && !mConnectingFlags.get(robot.getIdentifier())) { // startDiscovery時は接続しないようにする cRobot.disconnect(); } else { if (BuildConfig.DEBUG) { Log.d("TEST", "online"); } mConnectingFlags.remove(robot.getIdentifier()); SpheroManager.this.onConnected(cRobot); } break; case Connecting: if (BuildConfig.DEBUG) { Log.d("TEST", "connecting"); } mCounting.put(robot.getIdentifier(), 1); scanSphero(true); if (getNotConnectedDevice(robot.getIdentifier()) == null) { mFoundDevices.add(robot); } if (mDiscoveryListener != null) { mDiscoveryListener.onDeviceFound(cRobot); } break; case Connected: case Disconnected: case Offline: case FailedConnect: default: } } } @Override public void sensorUpdated(final DeviceInfo info, final DeviceSensorAsyncMessage data, final long interval) { if (mService == null) { return; } mCacheDeviceInfo = info; mCacheDeviceSensorsData = data; mCacheInterval = interval; List<Event> events = EventManager.INSTANCE.getEventList(info.getDevice().getRobot().getIdentifier(), DeviceOrientationProfile.PROFILE_NAME, null, DeviceOrientationProfile.ATTRIBUTE_ON_DEVICE_ORIENTATION); if (events.size() != 0) { Bundle orientation = createOrientation(data, interval); synchronized (events) { for (Event e : events) { Intent event = EventManager.createEventMessage(e); DeviceOrientationProfile.setOrientation(event, orientation); mService.sendEvent(event, e.getAccessToken()); } } } events = EventManager.INSTANCE.getEventList(info.getDevice().getRobot().getIdentifier(), SpheroProfile.PROFILE_NAME, SpheroProfile.INTER_QUATERNION, SpheroProfile.ATTR_ON_QUATERNION); if (events.size() != 0) { Bundle quaternion = createQuaternion(data, interval); synchronized (events) { for (Event e : events) { Intent event = EventManager.createEventMessage(e); event.putExtra(SpheroProfile.PARAM_QUATERNION, quaternion); mService.sendEvent(event, e.getAccessToken()); } } } events = EventManager.INSTANCE.getEventList(info.getDevice().getRobot().getIdentifier(), SpheroProfile.PROFILE_NAME, SpheroProfile.INTER_LOCATOR, SpheroProfile.ATTR_ON_LOCATOR); if (events.size() != 0) { Bundle locator = createLocator(data); synchronized (events) { for (Event e : events) { Intent event = EventManager.createEventMessage(e); event.putExtra(SpheroProfile.PARAM_LOCATOR, locator); mService.sendEvent(event, e.getAccessToken()); } } } } @Override public void collisionDetected(final DeviceInfo info, final CollisionDetectedAsyncData data) { if (mService == null) { return; } List<Event> events = EventManager.INSTANCE.getEventList(info.getDevice().getRobot().getIdentifier(), SpheroProfile.PROFILE_NAME, SpheroProfile.INTER_COLLISION, SpheroProfile.ATTR_ON_COLLISION); if (events.size() != 0) { Bundle collision = createCollision(data); synchronized (events) { for (Event e : events) { Intent event = EventManager.createEventMessage(e); event.putExtra(SpheroProfile.PARAM_COLLISION, collision); mService.sendEvent(event, e.getAccessToken()); } } } } /** * * @param enable */ public synchronized void scanSphero(final boolean enable) { if (enable) { if (mScanning || mScanTimerFuture != null) { // scan have already started. return; } mScanning = true; mScanTimerFuture = mExecutor.scheduleAtFixedRate(new Runnable() { @Override public void run() { for (String serviceId : mCounting.keySet()) { Integer count = mCounting.get(serviceId); count++; if (BuildConfig.DEBUG) { Log.d("TEST", "count:" + count); } mCounting.put(serviceId, count); if (count >= SEARCH_RETRY_NUM) { Robot info = getNotConnectedDevice(serviceId); if (mDiscoveryListener != null && info != null && !info.isConnected()) { mDiscoveryListener.onDeviceLost(new ConvenienceRobot(info)); } mCounting.put(serviceId, 0); if (mCounting.size() == 0) { mScanning = false; cancelScanTimer(); } } } } }, SCAN_FIRST_WAIT_PERIOD, SCAN_WAIT_PERIOD, TimeUnit.MILLISECONDS); } else { mScanning = false; cancelScanTimer(); } } /** * タイマーを止める. */ private synchronized void cancelScanTimer() { if (mScanTimerFuture != null) { mScanTimerFuture.cancel(true); mScanTimerFuture = null; } } }