/**
* ownCloud Android client application
*
* @author LukeOwncloud
* Copyright (C) 2016 ownCloud Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* 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 .
*
*/
package com.owncloud.android.files.services;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import com.owncloud.android.db.PreferenceManager;
import com.owncloud.android.db.UploadResult;
import com.owncloud.android.lib.common.utils.Log_OC;
/**
* Receives all connectivity action from Android OS at all times and performs
* required OC actions. For now that are: - Signal connectivity to
* {@link FileUploader}.
*
* Later can be added: - Signal connectivity to download service, deletion
* service, ... - Handle offline mode (cf.
* https://github.com/owncloud/android/issues/162)
*
* Have fun with the comments :S
*/
public class ConnectivityActionReceiver extends BroadcastReceiver {
private static final String TAG = ConnectivityActionReceiver.class.getSimpleName();
/**
* Magic keyword, by Google.
*
* {@See http://developer.android.com/intl/es/reference/android/net/wifi/WifiInfo.html#getSSID()}
*/
private static final String UNKNOWN_SSID = "";
@Override
public void onReceive(final Context context, Intent intent) {
// LOG ALL EVENTS:
Log_OC.v(TAG, "action: " + intent.getAction());
Log_OC.v(TAG, "component: " + intent.getComponent());
Bundle extras = intent.getExtras();
if (extras != null) {
for (String key : extras.keySet()) {
Log_OC.v(TAG, "key [" + key + "]: " + extras.get(key));
}
} else {
Log_OC.v(TAG, "no extras");
}
if (intent.getAction().equals(Intent.ACTION_POWER_CONNECTED) &&
(PreferenceManager.instantPictureUploadEnabled(context) &&
PreferenceManager.instantPictureUploadWhenChargingOnly(context)) ||
(PreferenceManager.instantVideoUploadEnabled(context) &&
PreferenceManager.instantVideoUploadWhenChargingOnly(context))
) {
// for the moment, only recovery of instant uploads, similar to behaviour in release 1.9.1
Log_OC.d(TAG, "Requesting retry of instant uploads (& friends) due to charging");
FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
requester.retryFailedUploads(
context,
null,
UploadResult.DELAYED_FOR_CHARGING // for the rest of enqueued when Wifi fell
);
}
/**
* There is an interesting mess to process WifiManager.NETWORK_STATE_CHANGED_ACTION and
* ConnectivityManager.CONNECTIVITY_ACTION in a simple and reliable way.
*
* The former triggers much more events than what we really need to know about Wifi connection.
*
* But there are annoying uncertainties about ConnectivityManager.CONNECTIVITY_ACTION due
* to the deprecation of ConnectivityManager.EXTRA_NETWORK_INFO in API level 14, and the absence
* of ConnectivityManager.EXTRA_NETWORK_TYPE until API level 17. Dear Google, how should we
* handle API levels 14 to 16?
*
* In the end maybe we need to keep in memory the current knowledge about connectivity
* and update it taking into account several Intents received in a row
*
* But first let's try something "simple" to keep a basic retry of instant uploads in
* version 1.9.2, similar to the existent until 1.9.1. To be improved.
*/
if(intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
NetworkInfo networkInfo =
intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
WifiInfo wifiInfo =
intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
String bssid =
intent.getStringExtra(WifiManager.EXTRA_BSSID);
if(networkInfo.isConnected() && // not enough; see (*) right below
wifiInfo != null &&
!UNKNOWN_SSID.equals(wifiInfo.getSSID().toLowerCase()) &&
bssid != null
) {
Log_OC.d(TAG, "WiFi connected");
wifiConnected(context);
} else {
// TODO tons of things to check to conclude disconnection;
// TODO maybe alternative commented below, based on CONNECTIVITY_ACTION is better
Log_OC.d(TAG, "WiFi disconnected ... but don't know if right now");
}
}
// (*) When WiFi is lost, an Intent with network state CONNECTED and SSID "" is
// received right before another Intent with network state DISCONNECTED; needs to
// be differentiated of a new Wifi connection.
//
// Besides, with a new connection two Intents are received, having only the second the extra
// WifiManager.EXTRA_BSSID, with the BSSID of the access point accessed.
//
// Not sure if this protocol is exact, since it's not documented. Only found mild references in
// - http://developer.android.com/intl/es/reference/android/net/wifi/WifiInfo.html#getSSID()
// - http://developer.android.com/intl/es/reference/android/net/wifi/WifiManager.html#EXTRA_BSSID
// and reproduced in Nexus 5 with Android 6.
/**
* Possible alternative attending ConnectivityManager.CONNECTIVITY_ACTION.
*
* Let's see what QA has to say
*
if(intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
NetworkInfo networkInfo = intent.getParcelableExtra(
ConnectivityManager.EXTRA_NETWORK_INFO // deprecated in API 14
);
int networkType = intent.getIntExtra(
ConnectivityManager.EXTRA_NETWORK_TYPE, // only from API level 17
-1
);
boolean couldBeWifiAction =
(networkInfo == null && networkType < 0) || // cases of lack of info
networkInfo.getType() == ConnectivityManager.TYPE_WIFI ||
networkType == ConnectivityManager.TYPE_WIFI;
if (couldBeWifiAction) {
if (ConnectivityUtils.isAppConnectedViaUnmeteredWiFi(context)) {
Log_OC.d(TAG, "WiFi connected");
wifiConnected(context);
} else {
Log_OC.d(TAG, "WiFi disconnected");
wifiDisconnected(context);
}
} /* else, CONNECTIVIY_ACTION is (probably) about other network interface (mobile, bluetooth, ...)
}
*/
}
private void wifiConnected(Context context) {
// for the moment, only recovery of instant uploads, similar to behaviour in release 1.9.1
if (
(PreferenceManager.instantPictureUploadEnabled(context) &&
PreferenceManager.instantPictureUploadViaWiFiOnly(context)) ||
(PreferenceManager.instantVideoUploadEnabled(context) &&
PreferenceManager.instantVideoUploadViaWiFiOnly(context))
) {
Log_OC.d(TAG, "Requesting retry of instant uploads (& friends)");
FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
requester.retryFailedUploads(
context,
null,
UploadResult.NETWORK_CONNECTION // for the interrupted when Wifi fell, if any
// (side effect: any upload failed due to network error will be retried too, instant or not)
);
requester.retryFailedUploads(
context,
null,
UploadResult.DELAYED_FOR_WIFI // for the rest of enqueued when Wifi fell
);
}
}
private void wifiDisconnected(Context context) {
// TODO something smart
// NOTE: explicit cancellation of only-wifi instant uploads is not needed anymore, since currently:
// - any upload in progress will be interrupted due to the lack of connectivity while the device
// reconnects through other network interface;
// - FileUploader checks instant upload settings and connection state before executing each
// upload operation, so other pending instant uploads after the current one will not be run
// (currently are silently moved to FAILED state)
}
static public void enableActionReceiver(Context context) {
PackageManager pm = context.getPackageManager();
ComponentName compName = new ComponentName(context.getApplicationContext(), ConnectivityActionReceiver.class);
pm.setComponentEnabledSetting(compName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
}
static public void disableActionReceiver(Context context) {
PackageManager pm = context.getPackageManager();
ComponentName compName = new ComponentName(context.getApplicationContext(), ConnectivityActionReceiver.class);
pm.setComponentEnabledSetting(compName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
}
}