syncthing.android.service.ServiceSettings.java Source code

Java tutorial

Introduction

Here is the source code for syncthing.android.service.ServiceSettings.java

Source

/*
 * Copyright (c) 2015 OpenSilk Productions LLC
 *
 * 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 syncthing.android.service;

import android.annotation.TargetApi;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.BatteryManager;
import android.os.Bundle;
import android.os.RemoteException;

import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.opensilk.common.core.dagger2.ForApplication;
import org.opensilk.common.core.util.BundleHelper;
import org.opensilk.common.core.util.VersionUtils;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import javax.inject.Inject;
import javax.inject.Named;

import timber.log.Timber;

/**
 * Created by drew on 3/21/15.
 */
public class ServiceSettings {

    public static final String ENABLED = "local_instance_enabled";
    public static final String INITIALISED = "local_instance_initialised";

    public static final String RUN_WHEN = "run_when";
    public static final String ALWAYS = "always";

    public static final String SCHEDULED = "scheduled";
    public static final String RANGE_START = "scheduled_start";
    public static final String RANGE_END = "scheduled_end";

    public static final String ONLY_WIFI = "only_on_wifi";
    public static final String WIFI_NETWORKS = "TRANSIENT_wifi_networks";
    public static final String ONLY_CHARGING = "only_when_charging";

    private final Context appContext;
    private final ConnectivityManager cm;
    private final WifiManager wm;
    private final Uri callUri;
    private final ReceiverHelper receiverHelper;

    private final Object clientLock = new Object();

    private ContentProviderClient client;
    private boolean cached;

    @Inject
    public ServiceSettings(@ForApplication Context appContext, ConnectivityManager cm, WifiManager wm,
            @Named("settingsAuthority") String authority, ReceiverHelper receiverHelper) {
        this.appContext = appContext;
        this.cm = cm;
        this.wm = wm;
        this.callUri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority).build();
        this.receiverHelper = receiverHelper;
    }

    private Bundle getCall(String pref, Bundle extras) {
        return makeCall("get_settings", pref, extras, true);
    }

    private Bundle putCall(String pref, Bundle extras) {
        return makeCall("put_settings", pref, extras, true);
    }

    private Bundle makeCall(String method, String pref, Bundle extras, boolean retry) {
        if (cached && VersionUtils.hasApi17()) {
            try {
                return makeCallApi17(method, pref, extras);
            } catch (RemoteException e) {
                release();
                if (retry) {
                    return makeCall(method, pref, extras, false);
                } else {
                    return extras;//return defaults
                }
            }
        } else {
            return appContext.getContentResolver().call(callUri, method, pref, extras);
        }
    }

    @TargetApi(17)
    private Bundle makeCallApi17(String method, String pref, Bundle extras) throws RemoteException {
        synchronized (clientLock) {
            if (client == null) {
                //clients dramatically improve performance and is what
                //content resolver does under the hood anyway.
                client = appContext.getContentResolver().acquireUnstableContentProviderClient(callUri);
                if (client == null) {
                    throw new RuntimeException("Unable to connect to our *own* content provider !!!!");
                }
            }
            return client.call(method, pref, extras);
        }
    }

    public void release() {
        synchronized (clientLock) {
            if (client != null) {
                client.release();
                client = null;
            }
        }
    }

    public void setCached(boolean cached) {
        this.cached = cached;
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        if (client != null) {
            Timber.e("Settings were not released!!!");
            release();
        }
    }

    public boolean isDisabled() {
        return !isEnabled();
    }

    public boolean isEnabled() {
        Bundle reply = getCall(ENABLED, BundleHelper.b().putInt(0).get());
        return BundleHelper.getInt(reply) == 1;
    }

    public void setEnabled(boolean enabled) {
        Bundle reply = putCall(ENABLED, BundleHelper.b().putInt(enabled ? 1 : 0).get());
        if (StringUtils.equals(BundleHelper.getString(reply), "ok")) {
            receiverHelper.setBootReceiverEnabled(enabled);
            if (enabled) {
                receiverHelper.setConnectivityReceiverEnabled(onlyOnWifi());
                receiverHelper.setChargingReceiverEnabled(onlyWhenCharging());
            } else {
                receiverHelper.setConnectivityReceiverEnabled(false);
                receiverHelper.setChargingReceiverEnabled(false);
            }
        }
    }

    public boolean isInitialised() {
        Bundle reply = getCall(INITIALISED, BundleHelper.b().putInt(0).get());
        return BundleHelper.getInt(reply) == 1;
    }

    public void setInitialized(boolean initialized) {
        Bundle reply = putCall(INITIALISED, BundleHelper.b().putInt(initialized ? 1 : 0).get());
        if (initialized && "ok".equals(BundleHelper.getString(reply))) {
            appContext.getContentResolver().notifyChange(getInitializedUri(), null);
        }
    }

    public String runWhen() {
        Bundle reply = getCall(RUN_WHEN, BundleHelper.b().putString(ALWAYS).get());
        return BundleHelper.getString(reply);
    }

    public void setRunWhen(String runWhen) {
        Bundle reply = putCall(RUN_WHEN, BundleHelper.b().putString(runWhen).get());
    }

    public boolean onlyWhenCharging() {
        Bundle reply = getCall(ONLY_CHARGING, BundleHelper.b().putInt(0).get());
        return BundleHelper.getInt(reply) == 1;
    }

    public void setOnlyWhenCharging(boolean onlyWhenCharging) {
        Bundle reply = putCall(ONLY_CHARGING, BundleHelper.b().putInt(onlyWhenCharging ? 1 : 0).get());
        if (StringUtils.equals(BundleHelper.getString(reply), "ok")) {
            receiverHelper.setChargingReceiverEnabled(onlyWhenCharging);
        }
    }

    public String getScheduledStartTime() {
        Bundle reply = getCall(RANGE_START, BundleHelper.b().putString("00:00").get());
        return BundleHelper.getString(reply);
    }

    public void setScheduledStartTime(String time) {
        Bundle reply = putCall(RANGE_START, BundleHelper.b().putString(time).get());
    }

    public String getScheduledEndTime() {
        Bundle reply = getCall(RANGE_END, BundleHelper.b().putString("00:00").get());
        return BundleHelper.getString(reply);
    }

    public void setScheduledEndTime(String time) {
        Bundle reply = putCall(RANGE_END, BundleHelper.b().putString(time).get());
    }

    public boolean onlyOnWifi() {
        Bundle reply = getCall(ONLY_WIFI, BundleHelper.b().putInt(0).get());
        return BundleHelper.getInt(reply) == 1;
    }

    public void setOnlyOnWifi(boolean onlyOnWifi) {
        Bundle reply = putCall(ONLY_WIFI, BundleHelper.b().putInt(onlyOnWifi ? 1 : 0).get());
        if (StringUtils.equals(BundleHelper.getString(reply), "ok")) {
            receiverHelper.setConnectivityReceiverEnabled(onlyOnWifi);
        }
    }

    public Set<String> allowedWifiNetworks() {
        Bundle reply = getCall(WIFI_NETWORKS, null);
        if (reply != null) {
            return unrollWifiNetworks(BundleHelper.getString(reply));
        } else {
            return Collections.emptySet();
        }
    }

    public void setAllowedWifiNetworks(Set<String> networks) {
        Bundle reply = putCall(WIFI_NETWORKS, BundleHelper.b().putString(rollWifiNetworks(networks)).get());
    }

    public Uri getInitializedUri() {
        return callUri.buildUpon().appendPath("instanceInitialized").build();
    }

    boolean isAllowedToRun() {
        if (isDisabled()) {
            Timber.d("isAllowedToRun(): SyncthingInstance disabled");
            return false;
        }
        if (!isInitialised()) {
            Timber.d("isAllowedToRun(): SyncthingInstance initiating credentials");
            return true;
        }
        if (onlyWhenCharging() && !isCharging()) {
            Timber.d("isAllowedToRun(): chargingOnly=true and not charging... rejecting");
            return false;
        }
        if (!hasSuitableConnection()) {
            Timber.d("isAllowedToRun(): No suitable network... rejecting");
            return false;
        }
        switch (runWhen()) {
        case SCHEDULED:
            long start = SyncthingUtils.parseTime(getScheduledStartTime());
            long end = SyncthingUtils.parseTime(getScheduledEndTime());
            boolean can = SyncthingUtils.isNowBetweenRange(start, end);
            Timber.d("isAllowedToRun(): is now a good time? %s", can);
            return can;
        case ALWAYS:
        default:
            Timber.d("isAllowedToRun(): Always mate!");
            return true;
        }
    }

    boolean isCharging() {
        IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        Intent battChanged = appContext.registerReceiver(null, filter);//stickies returned by register
        int status = battChanged != null ? battChanged.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) : 0;
        return status != 0;
    }

    boolean hasSuitableConnection() {
        NetworkInfo info = cm.getActiveNetworkInfo();
        if (onlyOnWifi()) {
            if (info != null && info.isConnectedOrConnecting()) {
                if (!isWifiOrEthernet(info.getType())) {
                    Timber.d("Connection is not wifi network");
                    return false;
                } else if (isWifi(info.getType()) && !isConnectedToWhitelistedNetwork()) {
                    Timber.d("Connected wifi network not in whitelist");
                    return false;
                } else {
                    Timber.d("Connected to wifi or ethernet");
                    return true;
                }
            } else {
                Timber.d("No wifi or ethernet connection");
                return false;
            }
        } else if (info != null && info.isConnectedOrConnecting()) {
            Timber.d("Connected to network");
            return true;
        } else {
            Timber.d("Not connected to any networks... running anyway");
            //TODO add setting to only run when connected
            return true;
        }
    }

    static boolean isWifi(int type) {
        return type == ConnectivityManager.TYPE_WIFI;
    }

    static boolean isWifiOrEthernet(int type) {
        return isWifi(type) || type == ConnectivityManager.TYPE_ETHERNET;
    }

    boolean isConnectedToWhitelistedNetwork() {
        Set<String> whitelist = allowedWifiNetworks();
        if (whitelist == null || whitelist.isEmpty()) {
            Timber.d("No whitelist found");
            return true;
        }
        WifiInfo info = wm.getConnectionInfo();
        if (info == null) {
            Timber.w("WifiInfo was null");
            return true;
        }
        String ssid = info.getSSID();
        if (ssid == null) {
            Timber.w("SSID was null");
            return true;
        }
        if (whitelist.contains(ssid)) {
            Timber.d("Found %s in whitelist", ssid);
            return true;
        } else {
            return false;
        }
    }

    boolean isOnSchedule() {
        return SCHEDULED.equals(runWhen());
    }

    long getNextScheduledEndTime() {
        long start = SyncthingUtils.parseTime(getScheduledStartTime());
        long end = SyncthingUtils.parseTime(getScheduledEndTime());
        DateTime now = DateTime.now();
        Interval interval = SyncthingUtils.getIntervalForRange(now, start, end);
        if (interval.contains(now)) {
            //With scheduled range
            return interval.getEndMillis();
        } else {
            //Outside scheduled range, shutdown asap
            return now.getMillis() + AlarmManagerHelper.KEEP_ALIVE;
        }
    }

    long getNextScheduledStartTime() {
        long start = SyncthingUtils.parseTime(getScheduledStartTime());
        long end = SyncthingUtils.parseTime(getScheduledEndTime());
        return getNextNextStartTimeFor(start, end);
    }

    static long getNextNextStartTimeFor(long start, long end) {
        DateTime now = DateTime.now();
        Interval interval = SyncthingUtils.getIntervalForRange(now, start, end);
        if (interval.isAfter(now)) {
            //Interval hasnt started yet
            return interval.getStartMillis();
        } else {
            //were either inside the interval or past it, get the next days start
            //XXX we count inside interval as next day so if user
            //    explicitly shuts us down we dont just start again
            return interval.getStart().plusDays(1).getMillis();
        }
    }

    static String rollWifiNetworks(Set<String> networks) {
        if (networks == null || networks.isEmpty()) {
            return "";
        }
        return StringUtils.join(networks, sep);
    }

    static Set<String> unrollWifiNetworks(String networks) {
        Set<String> set = new HashSet<>();
        String[] n = StringUtils.split(networks, sep);
        if (n != null && n.length > 0) {
            Collections.addAll(set, n);
        }
        return set;
    }

private static final char sep = '';//commas are boring

}