aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip P. Moltmann <moltmann@google.com>2016-02-11 09:56:57 -0800
committerPhilip P. Moltmann <moltmann@google.com>2016-02-11 09:56:57 -0800
commit419c9fadea20f8d7030beb6594a1b8b550706f3c (patch)
treeeb52f4bdce4055308d06c4b2e34268392db64b95
parentd17bfcdb71921def35a781a1ba4d16dbe9d2fcdb (diff)
downloadexperimental-419c9fadea20f8d7030beb6594a1b8b550706f3c.tar.gz
Printer Discovery: Start and stop discovery based on network connectivity.
Change-Id: Id85d0357fd5fbebf0c7e349261ea0c24a5c7bae7
-rw-r--r--PrintServiceStubs/AndroidManifest.xml2
-rw-r--r--PrintServiceStubs/src/com/android/printservicestubs/PrintServiceStubProvider.java4
-rw-r--r--PrintServiceStubs/src/com/android/printservicestubs/VendorConfig.java2
-rw-r--r--PrintServiceStubs/src/com/android/printservicestubs/servicediscovery/NetworkDiscovery.java263
4 files changed, 175 insertions, 96 deletions
diff --git a/PrintServiceStubs/AndroidManifest.xml b/PrintServiceStubs/AndroidManifest.xml
index 38f515c..2022a85 100644
--- a/PrintServiceStubs/AndroidManifest.xml
+++ b/PrintServiceStubs/AndroidManifest.xml
@@ -25,6 +25,8 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+ <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
+
<application
android:label="@string/app_name"
android:fullBackupContent="false"
diff --git a/PrintServiceStubs/src/com/android/printservicestubs/PrintServiceStubProvider.java b/PrintServiceStubs/src/com/android/printservicestubs/PrintServiceStubProvider.java
index 12cb804..d65bcdf 100644
--- a/PrintServiceStubs/src/com/android/printservicestubs/PrintServiceStubProvider.java
+++ b/PrintServiceStubs/src/com/android/printservicestubs/PrintServiceStubProvider.java
@@ -10,6 +10,7 @@ import android.database.*;
import android.net.Uri;
import android.util.Log;
import com.android.internal.util.Preconditions;
+import com.android.printservicestubs.servicediscovery.NetworkDiscovery;
import com.android.printservicestubs.stubs.gcp.GoogleCloudPrintStub;
import com.android.printservicestubs.stubs.mdnsFilter.MDNSFilterStub;
import com.android.printservicestubs.stubs.mopria.MopriaStub;
@@ -130,6 +131,9 @@ public class PrintServiceStubProvider extends ContentProvider
Preconditions.checkArgument(
PrintServiceStubContract.PrintServiceStubs.DEFAULT_SORT_ORDER.equals(sortOrder));
+ // There is a user of this provider, hence juice up the printer discovery
+ NetworkDiscovery.reactivate();
+
MatrixCursor c = new MatrixCursor(new String[] {
PrintServiceStubContract.PrintServiceStubs.NAME,
PrintServiceStubContract.PrintServiceStubs.IS_MULTIVENDOR_SERVICE,
diff --git a/PrintServiceStubs/src/com/android/printservicestubs/VendorConfig.java b/PrintServiceStubs/src/com/android/printservicestubs/VendorConfig.java
index 31ef3f5..fcb859b 100644
--- a/PrintServiceStubs/src/com/android/printservicestubs/VendorConfig.java
+++ b/PrintServiceStubs/src/com/android/printservicestubs/VendorConfig.java
@@ -130,7 +130,7 @@ public class VendorConfig {
*
* @param parser XML parser to read from
*
- * @return The test or "" if no text was found
+ * @return The text or "" if no text was found
*
* @throws IOException
* @throws XmlPullParserException
diff --git a/PrintServiceStubs/src/com/android/printservicestubs/servicediscovery/NetworkDiscovery.java b/PrintServiceStubs/src/com/android/printservicestubs/servicediscovery/NetworkDiscovery.java
index b7b1c6b..78f4cd1 100644
--- a/PrintServiceStubs/src/com/android/printservicestubs/servicediscovery/NetworkDiscovery.java
+++ b/PrintServiceStubs/src/com/android/printservicestubs/servicediscovery/NetworkDiscovery.java
@@ -19,7 +19,13 @@ package com.android.printservicestubs.servicediscovery;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.util.Log;
import com.android.internal.util.Preconditions;
import com.android.printservicestubs.R;
import com.android.printservicestubs.servicediscovery.mdns.MDnsDiscovery;
@@ -35,14 +41,17 @@ import java.util.LinkedHashMap;
* Discovers devices on the network and notifies the listeners about those devices.
*/
public class NetworkDiscovery {
+ private static final String LOG_TAG = "NetworkDiscovery";
+
/**
- * Currently active clients of the discovery
+ * Currently active clients of the discovery session.
*/
private static final @NonNull ArrayList<DiscoveryListener> sListeners = new ArrayList<>();
/**
* If the network discovery is running, this stores the instance. There can always be at most
- * once instance running.
+ * once instance running. The instance is automatically created when the first {@link
+ * #sListeners listener} is added and destroyed once the last one is removed.
*/
private static @Nullable NetworkDiscovery sInstance = null;
@@ -52,24 +61,67 @@ public class NetworkDiscovery {
private final @NonNull MDnsDiscovery mDiscoveryMethod;
/**
- * List of discovered printers sorted by device identifier
+ * List of discovered printers indexed by device identifier
*/
private final @NonNull LinkedHashMap<String, NetworkDevice> mDiscoveredPrinters;
/**
- * Socket used to discover devices (both query and listen).
+ * The context the discovery session runs in
*/
- private @NonNull MulticastSocket mSocket;
+ private final Context mContext;
/**
* Thread sending the broadcasts that should make the device announce them self
*/
- private @NonNull QueryThread mQueryThread;
+ private final @NonNull QueryThread mQueryThread;
/**
* Thread that receives the announcements of new devices and processes them
*/
- private @NonNull ListenerThread mListenerThread;
+ private final @NonNull ListenerThread mListenerThread;
+
+ /**
+ * Socket used to discover devices (both query and listen) if the discovery session is active.
+ */
+ private @NonNull MulticastSocket mSocket;
+
+ /**
+ * Create and start a new network discovery session.
+ *
+ * @param context The context requesting the start of the session.
+ *
+ * @throws IOException If the discovery session could not be started
+ */
+ private NetworkDiscovery(@NonNull Context context) throws IOException {
+ mContext = context;
+ mDiscoveredPrinters = new LinkedHashMap<>();
+
+ mDiscoveryMethod = new MDnsDiscovery(
+ context.getResources().getStringArray(R.array.mdns_services));
+
+ mListenerThread = new ListenerThread();
+ mQueryThread = new QueryThread();
+
+ startDiscovery();
+
+ // Stop or start session when network connection changes
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ try {
+ stopDiscovery();
+ } catch (InterruptedException e) {
+ // We cannot recover for this
+ throw new RuntimeException("Cannot stop session", e);
+ }
+
+ startDiscovery();
+ }
+ };
+
+ context.registerReceiver(receiver,
+ new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
+ }
/**
* Register a new listener for network devices. If this is the first listener, this starts a new
@@ -113,35 +165,87 @@ public class NetworkDiscovery {
sListeners.remove(listener);
if (sListeners.isEmpty()) {
- sInstance.close();
+ sInstance.stopDiscovery();
sInstance = null;
}
}
}
/**
- * Create and start a new network discovery session.
- *
- * @param context The context requesting the start of the session.
+ * Reactivate discovery, i.e. poll quickly for network printers.
+ */
+ public static void reactivate() {
+ // creation of the instance if synchronized by sListeners, see onListenerAdded and
+ // removeDiscoveryListener
+ synchronized (sListeners) {
+ if (sInstance != null) {
+ sInstance.mQueryThread.reactivate();
+ }
+ }
+ }
+
+ /**
+ * Start the session if the network is connected.
+ */
+ private void startDiscovery() {
+ synchronized (this) {
+ // if mSocket exist the discovery is running.
+ if (mSocket == null) {
+ ConnectivityManager cm =
+ (ConnectivityManager) mContext
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+
+ NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo();
+
+ if (activeNetworkInfo != null && activeNetworkInfo.isConnected()) {
+ // Recreate socket
+ try {
+ mSocket = NetworkUtils.createMulticastSocket(mContext, null);
+
+ mSocket.setBroadcast(true);
+ mSocket.setReuseAddress(true);
+ mSocket.setSoTimeout(0);
+
+ // Restart session
+ mListenerThread.start();
+ mQueryThread.start();
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Cannot create socket", e);
+ mSocket = null;
+ }
+
+ }
+ }
+ }
+ }
+
+ /**
+ * Stop the discovery session if a session is running.
*
- * @throws IOException If the discovery session could not be started
+ * @throws InterruptedException If the current thread was interrupted while the session was
+ * cleaned up.
*/
- private NetworkDiscovery(@NonNull Context context) throws IOException {
- mDiscoveredPrinters = new LinkedHashMap<>();
+ private void stopDiscovery() throws InterruptedException {
+ synchronized (this) {
+ if (mSocket != null) {
+ // Closing the socket causes IOExceptions on operations on this socket. This in turn
+ // will end the threads.
+ mSocket.close();
- mDiscoveryMethod = new MDnsDiscovery(
- context.getResources().getStringArray(R.array.mdns_services));
+ mListenerThread.join();
+ mQueryThread.join();
- mSocket = NetworkUtils.createMulticastSocket(context, null);
- mSocket.setBroadcast(true);
- mSocket.setReuseAddress(true);
- mSocket.setSoTimeout(0);
+ mSocket = null;
- mListenerThread = new ListenerThread();
- mQueryThread = new QueryThread();
+ synchronized (mDiscoveredPrinters) {
+ for (NetworkDevice device : mDiscoveredPrinters.values()) {
+ onDeviceRemoved(device);
+ }
- mListenerThread.start();
- mQueryThread.start();
+ mDiscoveredPrinters.clear();
+ }
+ }
+ }
}
/**
@@ -159,26 +263,11 @@ public class NetworkDiscovery {
}
/**
- * Clean up discovery session.
- *
- * @throws InterruptedException If the current thread was interrupted while the session was
- * cleaned up.
- */
- private void close() throws InterruptedException {
- // Closing the socket causes IOExceptions on operations on this socket. This in turn will
- // end the threads.
- mSocket.close();
-
- mListenerThread.join();
- mQueryThread.join();
- }
-
- /**
* Notify all currently registered listeners that a new device was removed.
*
* @param networkDevice The device that was removed
*/
- private void fireDeviceRemoved(@NonNull NetworkDevice networkDevice) {
+ private void onDeviceRemoved(@NonNull NetworkDevice networkDevice) {
synchronized (sListeners) {
final int numListeners = sListeners.size();
for (int i = 0; i < numListeners; i++) {
@@ -192,7 +281,7 @@ public class NetworkDiscovery {
*
* @param networkDevice The device that was found
*/
- private void fireDeviceFound(@NonNull NetworkDevice networkDevice) {
+ private void onDeviceFound(@NonNull NetworkDevice networkDevice) {
synchronized (sListeners) {
final int numListeners = sListeners.size();
for (int i = 0; i < numListeners; i++) {
@@ -202,7 +291,10 @@ public class NetworkDiscovery {
}
/**
- * Thread receiving and processing the packets that announce network devices
+ * Thread receiving and processing the packets that announce network devices.
+ * <p/>
+ * If devices are found or removed {@link #onDeviceFound(NetworkDevice)} and {@link
+ * #onDeviceRemoved(NetworkDevice)} are called.
*/
private class ListenerThread extends Thread {
private static final int BUFFER_LENGTH = 4 * 1024;
@@ -231,9 +323,9 @@ public class NetworkDiscovery {
NetworkDevice discoveredNetworkDevice = mDiscoveredPrinters.get(key);
if (discoveredNetworkDevice != null) {
- if(!device.getInetAddress()
- .equals(discoveredNetworkDevice.getInetAddress())) {
- fireDeviceRemoved(discoveredNetworkDevice);
+ if (!device.getInetAddress()
+ .equals(discoveredNetworkDevice.getInetAddress())) {
+ onDeviceRemoved(discoveredNetworkDevice);
} else {
discoveredNetworkDevice.addDiscoveryInstance(device);
device = discoveredNetworkDevice;
@@ -244,7 +336,7 @@ public class NetworkDiscovery {
}
}
- fireDeviceFound(device);
+ onDeviceFound(device);
}
} catch (IOException e) {
// Socket got closed
@@ -257,73 +349,54 @@ public class NetworkDiscovery {
/**
* Thread that sends out the packages that make the devices announce them self. The announcement
* are the received by the {@link ListenerThread listener thread}.
+ * <p/>
+ * For the fist {@value #FAST_QUERY_PERIOD_MS} ms the thread sends a query packet every {@value
+ * #FAST_DELAY_MS} ms. After that every {@value #SLOW_DELAY_MS} ms. The make the thread send the
+ * packets quickly again, {@link #reactivate()} can be called.
*/
private class QueryThread extends Thread {
- private static final int MAX_ACTIVE_QUERIES = 10;
- private static final int MAX_DELAY_SECONDS = 60;
-
- private boolean mUseFallback = false;
- private boolean mIsActiveDiscovery = true;
- private int mQueriesSent = 0;
+ private static final int FAST_DELAY_MS = 5_000;
+ private static final int FAST_QUERY_PERIOD_MS = 5 * 60_000;
+ private static final int SLOW_DELAY_MS = 20_000;
/**
- * @return the next wait interval, in milliseconds, using an exponential backoff algorithm.
+ * Last time {@link #reactivate} thread was reactivated or if never reactivated, the time
+ * the thread was created.
*/
- private int getQueryDelayInMillis() {
- int delayInSeconds = 1;
-
- int first = 1;
- int second = 1;
- int index;
+ private long mLastActivity;
- if (mQueriesSent > MAX_ACTIVE_QUERIES) {
- delayInSeconds = MAX_DELAY_SECONDS;
- } else {
-
- for (index = 1; index < mQueriesSent; index++) {
- if (index <= 1) {
- delayInSeconds = index;
- } else {
- delayInSeconds = first + second;
- first = second;
- second = delayInSeconds;
- }
- }
- }
+ public QueryThread() {
+ mLastActivity = System.currentTimeMillis();
+ }
- if (delayInSeconds >= MAX_DELAY_SECONDS) {
- delayInSeconds = MAX_DELAY_SECONDS;
- if (mIsActiveDiscovery) {
- mIsActiveDiscovery = false;
- }
+ /**
+ * Reactivate thread, i.e. make the thread send out the query packets quickly again
+ */
+ public void reactivate() {
+ synchronized (this) {
+ mLastActivity = System.currentTimeMillis();
+ notifyAll();
}
-
- return delayInSeconds * 1000;
}
@Override
public void run() {
- mQueriesSent = (mIsActiveDiscovery ? 0 : MAX_ACTIVE_QUERIES);
- mUseFallback = false;
-
while (true) {
try {
ArrayList<DatagramPacket> datagramList = new ArrayList<>();
+ Collections.addAll(datagramList, mDiscoveryMethod.createQueryPackets());
- if (!mDiscoveryMethod.isFallback() || mUseFallback) {
- Collections.addAll(datagramList, mDiscoveryMethod.createQueryPackets());
+ final int numPackets = datagramList.size();
+ for (int i = 0; i < numPackets; i++) {
+ mSocket.send(datagramList.get(i));
}
- for (DatagramPacket packet : datagramList) {
- mSocket.send(packet);
- }
-
- mQueriesSent++;
-
- Thread.sleep(getQueryDelayInMillis());
-
- if (!mUseFallback) {
- mUseFallback = mDiscoveredPrinters.isEmpty();
+ synchronized (this) {
+ if (System.currentTimeMillis() < mLastActivity + FAST_QUERY_PERIOD_MS) {
+ wait(FAST_DELAY_MS);
+ } else {
+ wait(SLOW_DELAY_MS);
+ }
}
} catch (Exception e) {
// Socket got closed