/******************************************************************************* * Copyright 2015 alladin-IT GmbH * * 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 * distributed 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 the specific language governing permissions and * limitations under the License. *******************************************************************************/ package at.alladin.rmbt.android.main.titlepage; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import android.app.Activity; import android.util.Log; import at.alladin.openrmbt.android.R; import at.alladin.rmbt.android.adapter.result.OnCompleteListener; import at.alladin.rmbt.android.util.CheckIpTask; import at.alladin.rmbt.android.util.CheckIpTask.IpVersionType; public class IpCheckRunnable implements Runnable { private final static String LOG = "IpCheckRunnable"; public final static int FLAG_IP_PRIVATE = 1; public final static int FLAG_IP_PUBLIC = 2; public final static int FLAG_IP_FINISHED = 3; public final static int FLAG_IP_CHECK_ERROR = -1; /** * ip fetch series count */ public final static int IP_FETCH_RETRIES = 3; /** * delay between a full ip fetch series */ public final static int IP_FETCH_RETRY_SERIES_DELAY = 10000; /** * poll delay */ public final static int IP_FETCH_POLL_DELAY = 5000; /** * */ public final static int IP_FETCH_POLL_SERIES_DELAY = 30000; public static interface OnIpCheckFinishedListener { void onFinish(final InetAddress privAddress, final InetAddress pubAddress, final InetAddress oldPrivAddress, final InetAddress oldPubAddress); } public class IpFetchController { public long lastRetryTimestamp = 0; public int retryCount = 0; public boolean isCheckAllowed(long currentTimestamp) { if (retryCount < IP_FETCH_RETRIES && ((lastRetryTimestamp + IP_FETCH_POLL_DELAY) <= currentTimestamp)) { return true; } if ((lastRetryTimestamp + IP_FETCH_POLL_SERIES_DELAY) <= currentTimestamp) { return true; } return false; } public void storeIpCheck(long timestamp) { this.retryCount++; this.lastRetryTimestamp = timestamp; } public int getRetryCount() { return retryCount; } public void reset() { this.lastRetryTimestamp = 0; this.retryCount = 0; } } public static enum IpStatus { STATUS_NOT_AVAILABLE(R.drawable.traffic_lights_grey), NO_ADDRESS(R.drawable.traffic_lights_red), ONLY_LOCAL(R.drawable.traffic_lights_yellow), CONNECTED_NAT(R.drawable.traffic_lights_yellow), CONNECTED_NO_NAT(R.drawable.traffic_lights_green); protected int resourceId; IpStatus(int resourceId) { this.resourceId = resourceId; } public int getResourceId() { return resourceId; } } private final AtomicBoolean needsIpCheck = new AtomicBoolean(true); private final AtomicBoolean isIpCheckRunning = new AtomicBoolean(false); private final AtomicBoolean hasIpFromControlServer = new AtomicBoolean(false); /** * strict ipv4/v6 = only accept ipv6 if ipVersionType is set to ipv6 etc. */ private final boolean isStrict; private InetAddress oldPrivAddress; private InetAddress oldPubAddress; private InetAddress privAddress; private InetAddress pubAddress; private final AtomicInteger ipCheckCounter = new AtomicInteger(0); private final IpFetchController ipFetchController = new IpFetchController(); private final Activity activity; private final IpVersionType ipVersionType; private final List<OnIpCheckFinishedListener> listenerList = new ArrayList<OnIpCheckFinishedListener>(); public IpCheckRunnable(final Activity activity, final IpVersionType ipVersionType, final boolean isStrict) { this.activity = activity; this.ipVersionType = ipVersionType; this.isStrict = isStrict; } public void addListener(final OnIpCheckFinishedListener listener) { if (!listenerList.contains(listener)) { listenerList.add(listener); } } /** * true if the list was modified, otherwise false * @param listener * @return */ public boolean removeListener(final OnIpCheckFinishedListener listener) { return listenerList.remove(listener); } @Override public void run() { if (ipFetchController.isCheckAllowed(System.currentTimeMillis()) && !isIpCheckRunning.get() && needsIpCheck.get()) { ipFetchController.storeIpCheck(System.currentTimeMillis()); isIpCheckRunning.set(true); needsIpCheck.set(false); ipCheckCounter.addAndGet(1); final CheckIpTask ipTask = new CheckIpTask(activity, ipVersionType); ipTask.setOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(int flag, Object object) { try { if (flag == FLAG_IP_PRIVATE) { if (ipCheckCounter.get()>0) { oldPrivAddress = privAddress; } privAddress = checkIp((InetAddress) object); } else if (flag == FLAG_IP_PUBLIC) { if (ipCheckCounter.get()>0) { oldPubAddress = pubAddress; } pubAddress = checkIp(InetAddress.getByName((String)object)); } else if (flag == FLAG_IP_FINISHED) { isIpCheckRunning.set(false); hasIpFromControlServer.set(true); for (OnIpCheckFinishedListener listener : listenerList) { listener.onFinish(privAddress, pubAddress, oldPrivAddress, oldPubAddress); } } else if (flag == FLAG_IP_CHECK_ERROR){ needsIpCheck.set(true); isIpCheckRunning.set(false); for (OnIpCheckFinishedListener listener : listenerList) { listener.onFinish(null, null, null, null); } } } catch (Exception e) { e.printStackTrace(); needsIpCheck.set(true); isIpCheckRunning.set(false); for (OnIpCheckFinishedListener listener : listenerList) { listener.onFinish(null, null, null, null); } } } }); ipTask.execute(); } } private InetAddress checkIp(final InetAddress inetAddress) { if (isStrict) { switch (ipVersionType) { case V4: if (!(inetAddress instanceof Inet4Address)) { Log.d(LOG, "v4 strict error bad address: " + inetAddress); return null; } break; case V6: if (!(inetAddress instanceof Inet6Address)) { Log.d(LOG, "v6 strict error bad address: " + inetAddress); return null; } break; } } return inetAddress; } public boolean hasPrivateIp() { return privAddress != null; } public IpStatus getIpStatus(IpCheckRunnable...checkRunnables) { if (hasIpFromControlServer.get()) { if (pubAddress != null && privAddress != null) { return pubAddress.equals(privAddress) ? IpStatus.CONNECTED_NO_NAT : IpStatus.CONNECTED_NAT; } return hasPrivateIp() ? IpStatus.ONLY_LOCAL : IpStatus.NO_ADDRESS; } boolean hasOtherIpFromControlServer = false; if (checkRunnables != null) { for (IpCheckRunnable r : checkRunnables) { if (r.getHasIpFromControlServer()) { hasOtherIpFromControlServer = true; break; } } } return hasOtherIpFromControlServer && ipCheckCounter.get() > 1 ? IpStatus.NO_ADDRESS : IpStatus.STATUS_NOT_AVAILABLE; } public void clearIps() { setPrivAddress(null); setPubAddress(null); needsIpCheck.set(true); ipFetchController.reset(); ipCheckCounter.set(0); hasIpFromControlServer.set(false); } public InetAddress getPrivAddress() { return privAddress; } public void setPrivAddress(InetAddress privAddress) { this.privAddress = privAddress; } public InetAddress getPubAddress() { return pubAddress; } public void setPubAddress(InetAddress pubAddress) { this.pubAddress = pubAddress; } public IpVersionType getIpVersionType() { return ipVersionType; } public boolean getHasIpFromControlServer() { return hasIpFromControlServer.get(); } }