com.adguard.android.service.FilterServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.adguard.android.service.FilterServiceImpl.java

Source

/**
 This file is part of Adguard Content Blocker (https://github.com/AdguardTeam/ContentBlocker).
 Copyright  2016 Performix LLC. All rights reserved.
    
 Adguard Content Blocker 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.
    
 Adguard Content Blocker 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
 Adguard Content Blocker.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.adguard.android.service;

import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;

import com.adguard.android.contentblocker.AlarmReceiver;
import com.adguard.android.contentblocker.R;
import com.adguard.android.ServiceLocator;
import com.adguard.android.db.FilterListDao;
import com.adguard.android.db.FilterListDaoImpl;
import com.adguard.android.db.FilterRuleDao;
import com.adguard.android.db.FilterRuleDaoImpl;
import com.adguard.android.contentblocker.ServiceApiClient;
import com.adguard.android.model.FilterList;
import com.adguard.commons.NetworkUtils;
import com.adguard.commons.concurrent.DispatcherThreadPool;
import com.adguard.commons.io.IoUtils;
import com.adguard.commons.InternetUtils;
import com.adguard.commons.web.UrlUtils;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
import java.util.regex.Pattern;

/**
 * Filter service implementation.
 */
public class FilterServiceImpl extends BaseUiService implements FilterService {
    private static final Logger LOG = LoggerFactory.getLogger(FilterServiceImpl.class);

    public static final int SHOW_USEFUL_ADS_FILTER_ID = 10;

    private static final int MIN_RULE_LENGTH = 4;
    private static final String ASCII_SYMBOL = "\\p{ASCII}+";
    private static final String COMMENT = "!";
    private static final String ADBLOCK_META_START = "[Adblock";
    private static final String MASK_OBSOLETE_SCRIPT_INJECTION = "###adg_start_script_inject";
    private static final String MASK_OBSOLETE_STYLE_INJECTION = "###adg_start_style_inject";

    private static final int SOCIAL_MEDIA_WIDGETS_FILTER_ID = 4;
    private static final int SPYWARE_FILTER_ID = 3;

    private static final int UPDATE_INVALIDATE_PERIOD = 24 * 60 * 60 * 1000; // 24 hours
    private static final int UPDATE_INITIAL_PERIOD = 60 * 1000; // 1 minutes

    private static final String FILTERS_UPDATE_QUEUE = "filters-update-queue";

    private final Context context;
    private final FilterListDao filterListDao;
    private final FilterRuleDao filterRuleDao;
    private final PreferencesService preferencesService;

    private int cachedFilterRuleCount = 0;

    /**
     * Creates an instance of AdguardService
     *
     * @param context Context
     */
    public FilterServiceImpl(Context context) {
        LOG.info("Creating AdguardService instance for {}", context);
        this.context = context;
        filterListDao = new FilterListDaoImpl(context);
        filterRuleDao = new FilterRuleDaoImpl(context);
        preferencesService = ServiceLocator.getInstance(context).getPreferencesService();
    }

    public static void enableContentBlocker(Context context) {
        Intent intent = new Intent();
        intent.setAction("com.samsung.android.sbrowser.contentBlocker.ACTION_UPDATE");
        intent.setData(Uri.parse("package:com.adguard.android.contentblocker"));
        context.sendBroadcast(intent);
    }

    @Override
    public void checkFiltersUpdates(Activity activity) {
        LOG.info("Start manual filters updates check");
        ServiceLocator.getInstance(activity.getApplicationContext()).getPreferencesService()
                .setLastUpdateCheck(new Date().getTime());

        ProgressDialog progressDialog = showProgressDialog(activity, R.string.checkUpdatesProgressDialogTitle,
                R.string.checkUpdatesProgressDialogMessage);
        DispatcherThreadPool.getInstance().submit(FILTERS_UPDATE_QUEUE,
                new CheckUpdatesTask(activity, progressDialog));
        LOG.info("Submitted filters update task");
    }

    @Override
    public List<FilterList> getFilters() {
        return filterListDao.selectFilterLists();
    }

    @Override
    public int getFilterListCount() {
        return filterListDao.getFilterListCount();
    }

    @Override
    public int getEnabledFilterListCount() {
        return filterListDao.getEnabledFilterListCount();
    }

    @Override
    public int getFilterRuleCount() {
        if (cachedFilterRuleCount == 0) {
            cachedFilterRuleCount = preferencesService.getFilterRuleCount();
        }
        return cachedFilterRuleCount;
    }

    @Override
    public List<FilterList> checkFilterUpdates(boolean force) {
        return checkOutdatedFilterUpdates(force);
    }

    @Override
    public void scheduleFiltersUpdate() {
        Intent alarmIntent = new Intent(AlarmReceiver.UPDATE_FILTER_ACTION);

        boolean isRunning = PendingIntent.getBroadcast(context, 0, alarmIntent,
                PendingIntent.FLAG_NO_CREATE) != null;
        if (!isRunning) {
            LOG.info("Starting scheduler of filters updating");
            PendingIntent broadcastIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, 0);

            AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
            alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, UPDATE_INITIAL_PERIOD, AlarmManager.INTERVAL_HOUR,
                    broadcastIntent);
        } else {
            LOG.info("Filters update is running");
        }
    }

    @Override
    public void updateFilterEnabled(FilterList filter, boolean enabled) {
        filter.setEnabled(enabled);
        filterListDao.updateFilterEnabled(filter, enabled);
    }

    @Override
    public Set<String> getWhiteList() {
        return preferencesService.getWhiteList();
    }

    @Override
    public void addToWhitelist(String item) {
        preferencesService.addToWhitelist(item);
    }

    @Override
    public void clearWhiteList() {
        preferencesService.clearWhiteList();
    }

    @Override
    public void removeWhiteListItem(String item) {
        preferencesService.removeWhiteListItem(item);
    }

    @Override
    public Set<String> getUserRules() {
        return preferencesService.getUserRules();
    }

    @Override
    public void addUserRuleItem(String item) {
        preferencesService.addUserRuleItem(item);
    }

    @Override
    public void removeUserRuleItem(String item) {
        preferencesService.removeUserRuleItem(item);
    }

    @Override
    public void clearUserRules() {
        preferencesService.clearUserRules();
    }

    @Override
    public void importUserRulesFromUrl(Activity activity, String url) {
        LOG.info("Start import user rules from {}", url);

        ProgressDialog progressDialog = showProgressDialog(activity, R.string.importUserRulesProgressDialogTitle,
                R.string.importUserRulesProgressDialogMessage);
        DispatcherThreadPool.getInstance().submit(new ImportUserRulesTask(activity, progressDialog, url));
        LOG.info("Submitted import user rules task");
    }

    @Override
    public List<String> getAllEnabledRules(boolean useCosmetics) {
        List<Integer> filterIds = getEnabledFilterIds();
        return filterRuleDao.selectRuleTexts(filterIds, useCosmetics);
    }

    @Override
    public List<Integer> getEnabledFilterIds() {
        List<Integer> filterIds = new ArrayList<>();
        for (FilterList filter : getEnabledFilters()) {
            filterIds.add(filter.getFilterId());
        }
        return filterIds;
    }

    @Override
    public boolean isShowUsefulAds() {
        final FilterList filter = filterListDao.selectFilterList(SHOW_USEFUL_ADS_FILTER_ID);
        return filter != null && filter.isEnabled();
    }

    @Override
    public void setShowUsefulAds(boolean value) {
        final FilterList filter = filterListDao.selectFilterList(SHOW_USEFUL_ADS_FILTER_ID);
        if (filter != null) {
            updateFilterEnabled(filter, value);
        }
    }

    @Override
    public boolean isSocialMediaWidgetsFilterEnabled() {
        final FilterList filter = filterListDao.selectFilterList(SOCIAL_MEDIA_WIDGETS_FILTER_ID);
        return filter != null && filter.isEnabled();
    }

    @Override
    public void setSocialMediaWidgetsFilterEnabled(boolean value) {
        final FilterList filter = filterListDao.selectFilterList(SOCIAL_MEDIA_WIDGETS_FILTER_ID);
        if (filter != null) {
            updateFilterEnabled(filter, value);
        }
    }

    @Override
    public boolean isSpywareFilterEnabled() {
        final FilterList filter = filterListDao.selectFilterList(SPYWARE_FILTER_ID);
        return filter != null && filter.isEnabled();
    }

    @Override
    public void applyNewSettings() {
        setShowUsefulAds(preferencesService.isShowUsefulAds());

        List<String> rules = getAllEnabledRules(true);
        Set<String> userRules = getUserRules();
        if (!userRules.isEmpty()) {
            for (String userRule : userRules) {
                userRule = StringUtils.trim(userRule);

                if (validateRuleText(userRule)) {
                    rules.add(userRule);
                }
            }
        }

        cachedFilterRuleCount = rules.size();

        try {
            LOG.info("Saving {} filters...", cachedFilterRuleCount);
            FileUtils.writeLines(new File(context.getFilesDir().getAbsolutePath() + "/filters.txt"), rules);
            preferencesService.setFilterRuleCount(cachedFilterRuleCount);
            enableContentBlocker(context);
        } catch (IOException e) {
            LOG.warn("Unable to save filters to file!!!", e);
        }
    }

    @Override
    public void setSpywareFilterEnabled(boolean value) {
        final FilterList filter = filterListDao.selectFilterList(SPYWARE_FILTER_ID);
        if (filter != null) {
            updateFilterEnabled(filter, value);
        }
    }

    /**
     * Checks the rules of non ascii symbols and control symbols
     *
     * @param userRule rule
     *
     * @return true if correct rule or false
     */
    private boolean validateRuleText(String userRule) {
        return StringUtils.isNotBlank(userRule) && userRule.matches(ASCII_SYMBOL)
                && StringUtils.length(userRule) > MIN_RULE_LENGTH && !StringUtils.startsWith(userRule, COMMENT)
                && !StringUtils.startsWith(userRule, ADBLOCK_META_START)
                && !StringUtils.contains(userRule, MASK_OBSOLETE_SCRIPT_INJECTION)
                && !StringUtils.contains(userRule, MASK_OBSOLETE_STYLE_INJECTION);
    }

    /**
     * Updates filters without updates for some time.
     *
     * @param force If true - updates not only over wifi
     * @return List of updated filters or null if something gone wrong
     */
    private List<FilterList> checkOutdatedFilterUpdates(boolean force) {
        if (!force) {
            if (!NetworkUtils.isNetworkAvailable(context) || !InternetUtils.isInternetAvailable()) {
                LOG.info("checkOutdatedFilterUpdates: internet is not available, doing nothing.");
                return new ArrayList<>();
            }

            if (preferencesService.isUpdateOverWifiOnly() && !NetworkUtils.isConnectionWifi(context)) {
                LOG.info("checkOutdatedFilterUpdates: Updates permitted over Wi-Fi only, doing nothing.");
                return new ArrayList<>();
            }

            boolean updateFilters = preferencesService.isAutoUpdateFilters();
            if (!updateFilters) {
                LOG.info("Filters auto-update is disabled, doing nothing");
                return new ArrayList<>();
            }
        }

        List<FilterList> filtersToUpdate = new ArrayList<>();
        long timeFromUpdate = System.currentTimeMillis() - UPDATE_INVALIDATE_PERIOD;
        for (FilterList filter : getEnabledFilters()) {

            if (force || (filter.getLastTimeDownloaded() == null)
                    || (filter.getLastTimeDownloaded().getTime() - timeFromUpdate < 0)) {
                filtersToUpdate.add(filter);
            }
        }

        return checkFilterUpdates(filtersToUpdate, force);
    }

    private List<FilterList> checkFilterUpdates(List<FilterList> filters, boolean force) {
        LOG.info("Start checking filters updates for {} outdated filters. Forced={}", filters.size(), force);

        if (CollectionUtils.isEmpty(filters)) {
            LOG.info("Empty filters list, doing nothing");
            return new ArrayList<>();
        }

        preferencesService.setLastUpdateCheck(new Date().getTime());

        try {
            final List<FilterList> updated = ServiceApiClient.downloadFilterVersions(context, filters);
            if (updated == null) {
                LOG.warn("Cannot download filter updates.");
                return null;
            }

            Map<Integer, FilterList> map = new HashMap<>();
            for (FilterList filter : updated) {
                map.put(filter.getFilterId(), filter);
            }

            for (FilterList current : filters) {
                final int filterId = current.getFilterId();
                if (!map.containsKey(filterId)) {
                    current.setLastTimeDownloaded(new Date());
                    updateFilter(current);
                    continue;
                }

                FilterList update = map.get(filterId);
                if (update.getVersion().compareTo(current.getVersion()) > 0) {
                    current.setVersion(update.getVersion().toString());
                    current.setLastTimeDownloaded(new Date());
                    current.setTimeUpdated(update.getTimeUpdated());
                    map.put(filterId, current);

                    LOG.info("Updating filter:" + current.getFilterId());
                    updateFilter(current);
                    LOG.info("Updating rules for filter:" + current.getFilterId());
                    updateFilterRules(filterId);
                } else {
                    map.remove(filterId);
                    current.setLastTimeDownloaded(new Date());
                    updateFilter(current);
                }
            }

            LOG.info("Finished checking filters updates.");

            return new ArrayList<>(map.values());
        } catch (IOException e) {
            LOG.error("Error checking filter updates:\r\n", e);
        } catch (Exception e) {
            LOG.error("Error parsing server response:\r\n", e);
        }

        return null;
    }

    private List<FilterList> getEnabledFilters() {
        List<FilterList> enabledFilters = new ArrayList<>();

        List<FilterList> filters = getFilters();
        for (FilterList filter : filters) {
            if (filter.isEnabled()) {
                enabledFilters.add(filter);
            }
        }

        LOG.info("Found {} enabled filters", enabledFilters.size());

        return enabledFilters;
    }

    private void updateFilterRules(int filterId) throws IOException {
        final List<String> rules = ServiceApiClient.downloadFilterRules(context, filterId);
        filterRuleDao.setFilterRules(filterId, rules);
    }

    private void updateFilter(FilterList current) {
        filterListDao.updateFilter(current);
    }

    /**
     * Task for importing user rules
     */
    private class ImportUserRulesTask extends LongRunningTask {

        private Activity activity;
        private String url;

        private OnImportListener onImportListener;

        public ImportUserRulesTask(Activity activity, ProgressDialog progressDialog, String url) {
            super(progressDialog);
            this.activity = activity;
            this.url = url;

            if (activity instanceof OnImportListener) {
                onImportListener = (OnImportListener) activity;
            }
        }

        @Override
        protected void processTask() throws Exception {
            LOG.info("Downloading user rules from {}", url);
            if (url.startsWith("content://")) {
                InputStream inputStream = null;
                try {
                    ContentResolver contentResolver = activity.getContentResolver();
                    inputStream = contentResolver.openInputStream(Uri.parse(url));
                    String buf = org.apache.commons.io.IOUtils.toString(inputStream);
                    importRules(buf);
                } finally {
                    IOUtils.closeQuietly(inputStream);
                }
                return;
            }

            String file = loadFromFile(url);
            final String download = file != null ? file : UrlUtils.downloadString(url);
            importRules(download);
        }

        private void importRules(String download) {
            final String[] rules = StringUtils.split(download, "\n");

            if (rules == null || rules.length < 1) {
                LOG.error("Error downloading user rules from {}", url);
                onError();
                return;
            }

            LOG.info("{} user rules downloaded from {}", rules.length);

            final List<String> rulesList = new ArrayList<>(rules.length);
            for (String rule : rules) {
                final String trimmedRule = rule.trim();
                if (StringUtils.isNotBlank(trimmedRule) && trimmedRule.length() < 8000) {
                    rulesList.add(trimmedRule);
                }
            }

            if (rulesList.size() < 1) {
                LOG.error("Invalid user rules from {}", url);
                onError();
                return;
            }

            preferencesService.addUserRuleItems(rulesList);
            LOG.info("User rules added successfully.");

            ServiceLocator.getInstance(activity.getApplicationContext()).getFilterService().applyNewSettings();

            String message = activity.getString(R.string.importUserRulesSuccessResultMessage).replace("{0}",
                    String.valueOf(rulesList.size()));
            showToast(activity, message);

            if (onImportListener != null) {
                activity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        onImportListener.onSuccess();
                    }
                });
            }
        }

        private void onError() {
            String message = activity.getString(R.string.importUserRulesErrorResultMessage);
            showToast(activity, message);
        }

        private String loadFromFile(String url) throws Exception {
            File f = new File(url);
            if (f.exists() && f.isFile() && f.canRead()) {
                FileInputStream fis = new FileInputStream(f);
                byte[] buf = IoUtils.readToEnd(fis);
                IOUtils.closeQuietly(fis);
                return new String(buf);
            }
            return null;
        }
    }

    public interface OnImportListener {
        void onSuccess();
    }

    /**
     * Task for checking updates
     */
    private class CheckUpdatesTask extends LongRunningTask {

        private Activity activity;

        public CheckUpdatesTask(Activity activity, ProgressDialog progressDialog) {
            super(progressDialog);
            this.activity = activity;
        }

        @Override
        protected void processTask() throws Exception {
            final List<FilterList> filters = ServiceLocator.getInstance(context).getFilterService()
                    .checkFilterUpdates(true);
            if (filters == null) {
                String message = activity.getString(R.string.checkUpdatesErrorResultMessage);
                showToast(activity, message);
            } else {
                if (filters.size() == 0) {
                    String message = activity.getString(R.string.checkUpdatesZeroResultMessage);
                    showToast(activity, message);
                } else {
                    if (filters.size() == 1) {
                        String message = activity.getString(R.string.checkUpdatesOneResultMessage).replace("{0}",
                                parseFilterNames(filters));
                        showToast(activity, message);
                    } else {
                        String message = activity.getString(R.string.checkUpdatesManyResultMessage)
                                .replace("{0}", Integer.toString(filters.size()))
                                .replace("{1}", parseFilterNames(filters));
                        showToast(activity, message);
                    }
                }
                preferencesService.setLastUpdateCheck(System.currentTimeMillis());
            }
            applyNewSettings();
        }

        private String parseFilterNames(List<FilterList> filters) {
            StringBuilder sb = new StringBuilder();
            for (FilterList filter : filters) {
                sb.append(" ");
                sb.append(filter.getName());
                sb.append(",");
            }

            if (sb.indexOf(",") > 0) {
                sb.deleteCharAt(sb.length() - 1);
            }

            return sb.toString();
        }
    }
}