NetGuard/app/src/main/java/eu/faircode/netguard/ServiceJob.java

254 lines
10 KiB
Java

package eu.faircode.netguard;
/*
This file is part of NetGuard.
NetGuard 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.
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
Copyright 2015-2016 by Marcel Bokhorst (M66B)
*/
import android.annotation.TargetApi;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.AsyncTask;
import android.os.Build;
import android.os.PersistableBundle;
import android.provider.Settings;
import android.util.Log;
import org.json.JSONObject;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URL;
import java.util.Date;
import java.util.Locale;
import javax.net.ssl.HttpsURLConnection;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class ServiceJob extends JobService {
private static int id = 0;
private static final String TAG = "NetGuard.Job";
private static final String cUrl = "https://crowd.netguard.me/";
private static final int cTimeOutMs = 15000;
@Override
public boolean onStartJob(JobParameters params) {
Log.i(TAG, "Start job=" + params.getJobId());
new AsyncTask<JobParameters, Object, Object>() {
@Override
protected JobParameters doInBackground(JobParameters... params) {
Log.i(TAG, "Executing job=" + params[0].getJobId());
HttpsURLConnection urlConnection = null;
try {
String android_id = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
JSONObject json = new JSONObject();
json.put("device", Util.sha256(android_id, ""));
json.put("product", Build.DEVICE);
json.put("sdk", Build.VERSION.SDK_INT);
json.put("country", Locale.getDefault().getCountry());
json.put("netguard", Util.getSelfVersionCode(ServiceJob.this));
try {
json.put("store", getPackageManager().getInstallerPackageName(getPackageName()));
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
json.put("store", null);
}
for (String name : params[0].getExtras().keySet())
json.put(name, params[0].getExtras().get(name));
urlConnection = (HttpsURLConnection) new URL(cUrl).openConnection();
urlConnection.setConnectTimeout(cTimeOutMs);
urlConnection.setReadTimeout(cTimeOutMs);
urlConnection.setRequestProperty("Accept", "application/json");
urlConnection.setRequestProperty("Content-type", "application/json");
urlConnection.setRequestMethod("POST");
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
out.write(json.toString().getBytes()); // UTF-8
out.flush();
int code = urlConnection.getResponseCode();
if (code != HttpsURLConnection.HTTP_OK)
throw new IOException("HTTP " + code);
InputStreamReader isr = new InputStreamReader(urlConnection.getInputStream());
Log.i(TAG, "Response=" + Util.readString(isr).toString());
jobFinished(params[0], false);
if ("rule".equals(params[0].getExtras().getString("type"))) {
SharedPreferences history = getSharedPreferences("history", Context.MODE_PRIVATE);
history.edit().putLong(params[0].getExtras().getString("package") + ":submitted", new Date().getTime()).apply();
}
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
jobFinished(params[0], true);
} finally {
if (urlConnection != null)
urlConnection.disconnect();
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
}
return null;
}
}.execute(params);
return true;
}
@Override
public boolean onStopJob(JobParameters params) {
Log.i(TAG, "Stop job=" + params.getJobId());
return true;
}
public static void submit(Rule rule, Context context) {
PersistableBundle bundle = new PersistableBundle();
bundle.putString("type", "rule");
bundle.putInt("wifi_default", rule.wifi_default ? 1 : 0);
bundle.putInt("other_default", rule.other_default ? 1 : 0);
bundle.putInt("screen_wifi_default", rule.screen_wifi_default ? 1 : 0);
bundle.putInt("screen_other_default", rule.screen_other_default ? 1 : 0);
bundle.putInt("roaming_default", rule.roaming_default ? 1 : 0);
bundle.putInt("wifi_blocked", rule.wifi_blocked ? 1 : 0);
bundle.putInt("other_blocked", rule.other_blocked ? 1 : 0);
bundle.putInt("screen_wifi", rule.screen_wifi ? 1 : 0);
bundle.putInt("screen_other", rule.screen_other ? 1 : 0);
bundle.putInt("roaming", rule.roaming ? 1 : 0);
bundle.putInt("apply", rule.apply ? 1 : 0);
bundle.putInt("notify", rule.notify ? 1 : 0);
submit(rule, bundle, context);
}
public static void submit(Rule rule, int version, int protocol, String daddr, int dport, int blocked, Context context) {
PersistableBundle bundle = new PersistableBundle();
bundle.putString("type", "host");
bundle.putInt("version", version);
bundle.putInt("protocol", protocol);
bundle.putString("daddr", daddr);
bundle.putInt("dport", dport);
bundle.putInt("blocked", blocked);
submit(rule, bundle, context);
}
private static void submit(Rule rule, PersistableBundle bundle, Context context) {
PackageManager pm = context.getPackageManager();
JobScheduler scheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
// Get english application label
String label = null;
try {
Configuration config = new Configuration();
config.setLocale(new Locale("en"));
Resources res = pm.getResourcesForApplication(rule.info.packageName);
res.updateConfiguration(config, res.getDisplayMetrics());
label = res.getString(rule.info.applicationInfo.labelRes);
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
CharSequence cs = rule.info.applicationInfo.loadLabel(pm);
if (cs != null)
label = cs.toString();
}
// Add application data
bundle.putInt("uid", rule.info.applicationInfo.uid);
bundle.putString("package", rule.info.packageName);
bundle.putInt("version_code", rule.info.versionCode);
bundle.putString("version_name", rule.info.versionName);
bundle.putString("label", label);
bundle.putInt("system", rule.system ? 1 : 0);
try {
bundle.putString("installer", pm.getInstallerPackageName(rule.info.packageName));
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
bundle.putString("installer", null);
}
// Cancel overlapping jobs
for (JobInfo pending : scheduler.getAllPendingJobs()) {
String type = pending.getExtras().getString("type");
if (type != null && type.equals(bundle.getString("type"))) {
if (type.equals("rule")) {
int uid = pending.getExtras().getInt("uid");
if (uid == bundle.getInt("uid")) {
Log.i(TAG, "Canceling id=" + pending.getId());
scheduler.cancel(pending.getId());
}
} else if (type.equals("host")) {
int uid = pending.getExtras().getInt("uid");
int version = pending.getExtras().getInt("version");
int protocol = pending.getExtras().getInt("protocol");
String daddr = pending.getExtras().getString("daddr");
int dport = pending.getExtras().getInt("dport");
if (uid == bundle.getInt("uid") &&
version == bundle.getInt("version") &&
protocol == bundle.getInt("protocol") &&
daddr != null && daddr.equals(bundle.getString("daddr")) &&
dport == bundle.getInt("dport")) {
Log.i(TAG, "Canceling id=" + pending.getId());
scheduler.cancel(pending.getId());
}
}
}
}
// Schedule job
ComponentName serviceName = new ComponentName(context, ServiceJob.class);
JobInfo job = new JobInfo.Builder(++id, serviceName)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
.setMinimumLatency(Util.isDebuggable(context) ? 10 * 1000 : 60 * 1000)
.setExtras(bundle)
.setPersisted(true)
.build();
if (scheduler.schedule(job) == JobScheduler.RESULT_SUCCESS)
Log.i(TAG, "Scheduled job=" + job.getId() + " success");
else
Log.e(TAG, "Scheduled job=" + job.getId() + " failed");
}
}