Java tutorial
/* * Copyright (C) 2011-2012 Dominik Schrmann <dominik@dominikschuermann.de> * * This file is part of AdAway. * * AdAway 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. * * AdAway 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 AdAway. If not, see <http://www.gnu.org/licenses/>. * */ package org.adawaycn.service; import android.support.v4.app.NotificationCompat; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.database.Cursor; import com.commonsware.cwac.wakeful.WakefulIntentService; import org.adawaycn.R; import org.adawaycn.helper.PreferenceHelper; import org.adawaycn.helper.ResultHelper; import org.adawaycn.provider.AdAwayContract.HostsSources; import org.adawaycn.provider.ProviderHelper; import org.adawaycn.ui.BaseActivity; import org.adawaycn.util.ApplyUtils; import org.adawaycn.util.CommandException; import org.adawaycn.util.Constants; import org.adawaycn.util.HostsParser; import org.adawaycn.util.Log; import org.adawaycn.util.NotEnoughSpaceException; import org.adawaycn.util.RemountException; import org.adawaycn.util.StatusCodes; import org.adawaycn.util.Utils; import org.sufficientlysecure.rootcommands.Shell; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; public class ApplyService extends WakefulIntentService { private Context mService; private NotificationManager mNotificationManager; private int mNumberOfFailedDownloads; private int mNumberOfDownloads; private static final int APPLY_NOTIFICATION_ID = 20; public ApplyService() { super("AdAwayApplyService"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { mService = this; mNotificationManager = (NotificationManager) mService.getApplicationContext() .getSystemService(Context.NOTIFICATION_SERVICE); return super.onStartCommand(intent, flags, startId); } /** * Asynchronous background operations of service, with wakelock */ @Override public void doWakefulWork(Intent intent) { // disable buttons BaseActivity.setButtonsDisabledBroadcast(mService, true); // download files with download method int downloadResult = download(); Log.d(Constants.TAG, "Download result: " + downloadResult); if (downloadResult == StatusCodes.SUCCESS) { // Apply files by apply method int applyResult = apply(); cancelApplyNotification(); // enable buttons BaseActivity.setButtonsDisabledBroadcast(mService, false); Log.d(Constants.TAG, "Apply result: " + applyResult); String successfulDownloads = (mNumberOfDownloads - mNumberOfFailedDownloads) + "/" + mNumberOfDownloads; ResultHelper.showNotificationBasedOnResult(mService, applyResult, successfulDownloads); } else if (downloadResult == StatusCodes.DOWNLOAD_FAIL) { cancelApplyNotification(); // enable buttons BaseActivity.setButtonsDisabledBroadcast(mService, false); // extra information is current url, to show it when it fails ResultHelper.showNotificationBasedOnResult(mService, downloadResult, null); } else { cancelApplyNotification(); // enable buttons BaseActivity.setButtonsDisabledBroadcast(mService, false); ResultHelper.showNotificationBasedOnResult(mService, downloadResult, null); } } /** * Downloads files from hosts sources * * @return return code */ private int download() { Cursor enabledHostsSourcesCursor; byte data[]; int count; long currentLastModifiedOnline; int returnCode = StatusCodes.SUCCESS; // default return code if (Utils.isAndroidOnline(mService)) { showApplyNotification(mService, mService.getString(R.string.download_dialog), mService.getString(R.string.download_dialog), mService.getString(R.string.download_dialog)); // output to write into FileOutputStream out = null; try { out = mService.openFileOutput(Constants.DOWNLOADED_HOSTS_FILENAME, Context.MODE_PRIVATE); mNumberOfFailedDownloads = 0; mNumberOfDownloads = 0; // get cursor over all enabled hosts source enabledHostsSourcesCursor = ProviderHelper.getEnabledHostsSourcesCursor(mService); // iterate over all hosts sources in db with cursor if (enabledHostsSourcesCursor.moveToFirst()) { do { mNumberOfDownloads++; InputStream is = null; BufferedInputStream bis = null; String currentUrl = enabledHostsSourcesCursor .getString(enabledHostsSourcesCursor.getColumnIndex("url")); try { Log.v(Constants.TAG, "Downloading hosts file: " + currentUrl); /* change URL in download dialog */ updateApplyNotification(mService, mService.getString(R.string.download_dialog), currentUrl); /* build connection */ URL mURL = new URL(currentUrl); URLConnection connection = mURL.openConnection(); connection.setConnectTimeout(15000); connection.setReadTimeout(30000); /* connect */ connection.connect(); is = connection.getInputStream(); bis = new BufferedInputStream(is); if (is == null) { Log.e(Constants.TAG, "Stream is null"); } /* download with progress */ data = new byte[1024]; count = 0; // run while only when thread is not cancelled while ((count = bis.read(data)) != -1) { out.write(data, 0, count); } // add line seperator to add files together in one file out.write(Constants.LINE_SEPERATOR.getBytes()); // save last modified online for later use currentLastModifiedOnline = connection.getLastModified(); ProviderHelper.updateHostsSourceLastModifiedOnline(mService, enabledHostsSourcesCursor .getInt(enabledHostsSourcesCursor.getColumnIndex(HostsSources._ID)), currentLastModifiedOnline); } catch (IOException e) { Log.e(Constants.TAG, "Exception while downloading from " + currentUrl, e); mNumberOfFailedDownloads++; // set last_modified_online of failed download to 0 (not available) ProviderHelper.updateHostsSourceLastModifiedOnline(mService, enabledHostsSourcesCursor .getInt(enabledHostsSourcesCursor.getColumnIndex(HostsSources._ID)), 0); } finally { // flush and close streams try { if (out != null) { out.flush(); } if (bis != null) { bis.close(); } if (is != null) { is.close(); } } catch (Exception e) { Log.e(Constants.TAG, "Exception on flush and closing streams.", e); } } } while (enabledHostsSourcesCursor.moveToNext()); } // close cursor in the end if (enabledHostsSourcesCursor != null && !enabledHostsSourcesCursor.isClosed()) { enabledHostsSourcesCursor.close(); } // if all downloads failed return download_fail error if (mNumberOfDownloads == mNumberOfFailedDownloads && mNumberOfDownloads != 0) { returnCode = StatusCodes.DOWNLOAD_FAIL; } } catch (Exception e) { Log.e(Constants.TAG, "Private File can not be created, Exception: " + e); returnCode = StatusCodes.PRIVATE_FILE_FAIL; } finally { try { if (out != null) { out.close(); } } catch (Exception e) { Log.e(Constants.TAG, "Exception on close of out.", e); } } } else { returnCode = StatusCodes.NO_CONNECTION; } return returnCode; } /** * Apply hosts file * * @return return code */ int apply() { showApplyNotification(mService, mService.getString(R.string.apply_dialog), mService.getString(R.string.apply_dialog), mService.getString(R.string.apply_dialog_hostnames)); int returnCode = StatusCodes.SUCCESS; // default return code BufferedOutputStream bos = null; try { /* PARSE: parse hosts files to sets of hostnames and comments */ FileInputStream fis = mService.openFileInput(Constants.DOWNLOADED_HOSTS_FILENAME); BufferedReader reader = new BufferedReader(new InputStreamReader(fis)); // Use whitelist and/or redirection rules from hosts sources only if enabled in preferences HostsParser parser = new HostsParser(reader, PreferenceHelper.getWhitelistRules(mService), PreferenceHelper.getRedirectionRules(mService)); fis.close(); updateApplyNotification(mService, mService.getString(R.string.apply_dialog), mService.getString(R.string.apply_dialog_lists)); /* READ DATABSE CONTENT */ // add whitelist from db parser.addWhitelist(ProviderHelper.getEnabledWhitelistHashSet(mService)); // add blacklist from db parser.addBlacklist(ProviderHelper.getEnabledBlacklistHashSet(mService)); // add redirection list from db parser.addRedirectionList(ProviderHelper.getEnabledRedirectionListHashMap(mService)); // get hosts sources list from db ArrayList<String> enabledHostsSources = ProviderHelper.getEnabledHostsSourcesArrayList(mService); Log.d(Constants.TAG, "Enabled hosts sources list: " + enabledHostsSources.toString()); // compile lists (removing whitelist entries, etc.) parser.compileList(); /* BUILD: build one hosts file out of sets and preferences */ updateApplyNotification(mService, mService.getString(R.string.apply_dialog), mService.getString(R.string.apply_dialog_hosts)); FileOutputStream fos = mService.openFileOutput(Constants.HOSTS_FILENAME, Context.MODE_PRIVATE); bos = new BufferedOutputStream(fos); // build current timestamp for header SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date now = new Date(); // add adaway header String header = Constants.HEADER1 + Constants.LINE_SEPERATOR + "# " + formatter.format(now) + Constants.LINE_SEPERATOR + Constants.HEADER2 + Constants.LINE_SEPERATOR + Constants.HEADER_SOURCES; bos.write(header.getBytes()); // write sources into header String source = null; for (String host : enabledHostsSources) { source = Constants.LINE_SEPERATOR + "# " + host; bos.write(source.getBytes()); } bos.write(Constants.LINE_SEPERATOR.getBytes()); String redirectionIP = PreferenceHelper.getRedirectionIP(mService); // add "127.0.0.1 localhost" entry String localhost = Constants.LINE_SEPERATOR + Constants.LOCALHOST_IPv4 + " " + Constants.LOCALHOST_HOSTNAME + Constants.LINE_SEPERATOR + Constants.LOCALHOST_IPv6 + " " + Constants.LOCALHOST_HOSTNAME; bos.write(localhost.getBytes()); bos.write(Constants.LINE_SEPERATOR.getBytes()); // write hostnames String line; String linev6; if (PreferenceHelper.getEnableIpv6(mService)) { for (String hostname : parser.getBlacklist()) { line = Constants.LINE_SEPERATOR + redirectionIP + " " + hostname; linev6 = Constants.LINE_SEPERATOR + "::1" + " " + hostname; bos.write(line.getBytes()); bos.write(linev6.getBytes()); } } else { for (String hostname : parser.getBlacklist()) { line = Constants.LINE_SEPERATOR + redirectionIP + " " + hostname; bos.write(line.getBytes()); } } /* REDIRECTION LIST: write redirection items */ String redirectionItemHostname; String redirectionItemIP; for (HashMap.Entry<String, String> item : parser.getRedirectionList().entrySet()) { redirectionItemHostname = item.getKey(); redirectionItemIP = item.getValue(); line = Constants.LINE_SEPERATOR + redirectionItemIP + " " + redirectionItemHostname; bos.write(line.getBytes()); } // hosts file has to end with new line, when not done last entry won't be // recognized bos.write(Constants.LINE_SEPERATOR.getBytes()); } catch (FileNotFoundException e) { Log.e(Constants.TAG, "file to read or file to write could not be found", e); returnCode = StatusCodes.PRIVATE_FILE_FAIL; } catch (IOException e) { Log.e(Constants.TAG, "files can not be written or read", e); returnCode = StatusCodes.PRIVATE_FILE_FAIL; } finally { try { if (bos != null) { bos.flush(); bos.close(); } } catch (Exception e) { Log.e(Constants.TAG, "Error closing output streams", e); } } // delete downloaded hosts file from private storage mService.deleteFile(Constants.DOWNLOADED_HOSTS_FILENAME); /* APPLY: apply hosts file using RootTools in copyHostsFile() */ updateApplyNotification(mService, mService.getString(R.string.apply_dialog), mService.getString(R.string.apply_dialog_apply)); Shell rootShell = null; try { rootShell = Shell.startRootShell(); } catch (Exception e) { Log.e(Constants.TAG, "Problem opening a root shell!", e); } // copy build hosts file with RootTools, based on target from preferences try { if (PreferenceHelper.getApplyMethod(mService).equals("writeToSystem")) { ApplyUtils.copyHostsFile(mService, Constants.ANDROID_SYSTEM_ETC_HOSTS, rootShell); } else if (PreferenceHelper.getApplyMethod(mService).equals("writeToDataData")) { ApplyUtils.copyHostsFile(mService, Constants.ANDROID_DATA_DATA_HOSTS, rootShell); } else if (PreferenceHelper.getApplyMethod(mService).equals("writeToData")) { ApplyUtils.copyHostsFile(mService, Constants.ANDROID_DATA_HOSTS, rootShell); } else if (PreferenceHelper.getApplyMethod(mService).equals("customTarget")) { ApplyUtils.copyHostsFile(mService, PreferenceHelper.getCustomTarget(mService), rootShell); } } catch (NotEnoughSpaceException e) { Log.e(Constants.TAG, "Exception: ", e); returnCode = StatusCodes.NOT_ENOUGH_SPACE; } catch (RemountException e) { Log.e(Constants.TAG, "Exception: ", e); returnCode = StatusCodes.REMOUNT_FAIL; } catch (CommandException e) { Log.e(Constants.TAG, "Exception: ", e); returnCode = StatusCodes.COPY_FAIL; } // delete generated hosts file from private storage mService.deleteFile(Constants.HOSTS_FILENAME); /* * Set last_modified_local dates in database to last_modified_online, got in download task */ ProviderHelper.updateAllEnabledHostsSourcesLastModifiedLocalFromOnline(mService); /* check if hosts file is applied with chosen method */ // check only if everything before was successful if (returnCode == StatusCodes.SUCCESS) { if (PreferenceHelper.getApplyMethod(mService).equals("writeToSystem")) { /* /system/etc/hosts */ if (!ApplyUtils.isHostsFileCorrect(mService, Constants.ANDROID_SYSTEM_ETC_HOSTS)) { returnCode = StatusCodes.APPLY_FAIL; } } else if (PreferenceHelper.getApplyMethod(mService).equals("writeToDataData")) { /* /data/data/hosts */ if (!ApplyUtils.isHostsFileCorrect(mService, Constants.ANDROID_DATA_DATA_HOSTS)) { returnCode = StatusCodes.APPLY_FAIL; } else { if (!ApplyUtils.isSymlinkCorrect(Constants.ANDROID_DATA_DATA_HOSTS, rootShell)) { returnCode = StatusCodes.SYMLINK_MISSING; } } } else if (PreferenceHelper.getApplyMethod(mService).equals("writeToData")) { /* /data/data/hosts */ if (!ApplyUtils.isHostsFileCorrect(mService, Constants.ANDROID_DATA_HOSTS)) { returnCode = StatusCodes.APPLY_FAIL; } else { if (!ApplyUtils.isSymlinkCorrect(Constants.ANDROID_DATA_HOSTS, rootShell)) { returnCode = StatusCodes.SYMLINK_MISSING; } } } else if (PreferenceHelper.getApplyMethod(mService).equals("customTarget")) { /* custom target */ String customTarget = PreferenceHelper.getCustomTarget(mService); if (!ApplyUtils.isHostsFileCorrect(mService, customTarget)) { returnCode = StatusCodes.APPLY_FAIL; } else { if (!ApplyUtils.isSymlinkCorrect(customTarget, rootShell)) { returnCode = StatusCodes.SYMLINK_MISSING; } } } } try { rootShell.close(); } catch (Exception e) { Log.e(Constants.TAG, "Problem closing the root shell!", e); } /* check if APN proxy is set */ if (returnCode == StatusCodes.SUCCESS) { if (ApplyUtils.isApnProxySet(mService)) { Log.d(Constants.TAG, "APN proxy is set!"); returnCode = StatusCodes.APN_PROXY; } } return returnCode; } /** * Creates custom made notification with progress */ private void showApplyNotification(Context context, String tickerText, String contentTitle, String contentText) { // configure the intent Intent intent = new Intent(mService, BaseActivity.class); PendingIntent contentIntent = PendingIntent.getActivity(mService.getApplicationContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); // add app name to notificationText tickerText = mService.getString(R.string.app_name) + ": " + tickerText; int icon = R.drawable.status_bar_icon; long when = System.currentTimeMillis(); // add app name to title String contentTitleWithAppName = mService.getString(R.string.app_name) + ": " + contentTitle; NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context).setSmallIcon(icon) .setContentTitle(contentTitleWithAppName).setTicker(tickerText).setWhen(when).setOngoing(true) .setOnlyAlertOnce(true).setContentText(contentText); mNotificationManager.notify(APPLY_NOTIFICATION_ID, mBuilder.build()); mBuilder.setContentIntent(contentIntent); // update status in BaseActivity with Broadcast BaseActivity.setStatusBroadcast(mService, contentTitle, contentText, StatusCodes.CHECKING); } private void updateApplyNotification(Context context, String contentTitle, String contentText) { // configure the intent Intent intent = new Intent(mService, BaseActivity.class); PendingIntent contentIntent = PendingIntent.getActivity(mService.getApplicationContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); int icon = R.drawable.status_bar_icon; // add app name to title String contentTitleWithAppName = mService.getString(R.string.app_name) + ": " + contentTitle; NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context).setSmallIcon(icon) .setContentTitle(contentTitleWithAppName).setContentText(contentText); mNotificationManager.notify(APPLY_NOTIFICATION_ID, mBuilder.build()); mBuilder.setContentIntent(contentIntent); // update status in BaseActivity with Broadcast BaseActivity.setStatusBroadcast(mService, contentTitle, contentText, StatusCodes.CHECKING); } private void cancelApplyNotification() { mNotificationManager.cancel(APPLY_NOTIFICATION_ID); } }