Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9d92d3e0 authored by Jean Chalard's avatar Jean Chalard
Browse files

Avoid downloading the metadata in quick succession.

Bug: 17668298
Bug: 8651816
Bug: 17709442
Change-Id: Ib232f9d95d7c01a028be85d10f974a5a570c0ed5
parent a4a4e9d9
Loading
Loading
Loading
Loading
+19 −9
Original line number Diff line number Diff line
@@ -76,19 +76,29 @@ public final class DictionaryService extends Service {
     * How often, in milliseconds, we want to update the metadata. This is a
     * floor value; actually, it may happen several hours later, or even more.
     */
    private static final long UPDATE_FREQUENCY = TimeUnit.DAYS.toMillis(4);
    private static final long UPDATE_FREQUENCY_MILLIS = TimeUnit.DAYS.toMillis(4);

    /**
     * We are waked around midnight, local time. We want to wake between midnight and 6 am,
     * roughly. So use a random time between 0 and this delay.
     */
    private static final int MAX_ALARM_DELAY = (int)TimeUnit.HOURS.toMillis(6);
    private static final int MAX_ALARM_DELAY_MILLIS = (int)TimeUnit.HOURS.toMillis(6);

    /**
     * How long we consider a "very long time". If no update took place in this time,
     * the content provider will trigger an update in the background.
     */
    private static final long VERY_LONG_TIME = TimeUnit.DAYS.toMillis(14);
    private static final long VERY_LONG_TIME_MILLIS = TimeUnit.DAYS.toMillis(14);

    /**
     * After starting a download, how long we wait before considering it may be stuck. After this
     * period is elapsed, if the keyboard tries to download again, then we cancel and re-register
     * the request; if it's within this time, we just leave it be.
     * It's important to note that we do not re-submit the request merely because the time is up.
     * This is only to decide whether to cancel the old one and re-requesting when the keyboard
     * fires a new request for the same data.
     */
    public static final long NO_CANCEL_DOWNLOAD_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(30);

    /**
     * An executor that serializes tasks given to it.
@@ -188,16 +198,16 @@ public final class DictionaryService extends Service {
     */
    private static void checkTimeAndMaybeSetupUpdateAlarm(final Context context) {
        // Of all clients, if the one that hasn't been updated for the longest
        // is still more recent than UPDATE_FREQUENCY, do nothing.
        if (!isLastUpdateAtLeastThisOld(context, UPDATE_FREQUENCY)) return;
        // is still more recent than UPDATE_FREQUENCY_MILLIS, do nothing.
        if (!isLastUpdateAtLeastThisOld(context, UPDATE_FREQUENCY_MILLIS)) return;

        PrivateLog.log("Date changed - registering alarm");
        AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);

        // Best effort to wake between midnight and MAX_ALARM_DELAY in the morning.
        // Best effort to wake between midnight and MAX_ALARM_DELAY_MILLIS in the morning.
        // It doesn't matter too much if this is very inexact.
        final long now = System.currentTimeMillis();
        final long alarmTime = now + new Random().nextInt(MAX_ALARM_DELAY);
        final long alarmTime = now + new Random().nextInt(MAX_ALARM_DELAY_MILLIS);
        final Intent updateIntent = new Intent(DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION);
        final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
                updateIntent, PendingIntent.FLAG_CANCEL_CURRENT);
@@ -223,11 +233,11 @@ public final class DictionaryService extends Service {
    /**
     * Refreshes data if it hasn't been refreshed in a very long time.
     *
     * This will check the last update time, and if it's been more than VERY_LONG_TIME,
     * This will check the last update time, and if it's been more than VERY_LONG_TIME_MILLIS,
     * update metadata now - and possibly take subsequent update actions.
     */
    public static void updateNowIfNotUpdatedInAVeryLongTime(final Context context) {
        if (!isLastUpdateAtLeastThisOld(context, VERY_LONG_TIME)) return;
        if (!isLastUpdateAtLeastThisOld(context, VERY_LONG_TIME_MILLIS)) return;
        UpdateHandler.tryUpdate(context, false);
    }

+29 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.inputmethod.dictionarypack;

/**
 * A simple container of download ID and download start date.
 */
public class DownloadIdAndStartDate {
    public final long mId;
    public final long mStartDate;
    public DownloadIdAndStartDate(final long id, final long startDate) {
        mId = id;
        mStartDate = startDate;
    }
}
+7 −6
Original line number Diff line number Diff line
@@ -433,18 +433,18 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
     *
     * @param context a context instance to open the database on
     * @param uri the URI to retrieve the metadata download ID of
     * @return the metadata download ID, or NOT_AN_ID if no download is in progress
     * @return the download id and start date, or null if the URL is not known
     */
    public static long getMetadataDownloadIdForURI(final Context context,
            final String uri) {
    public static DownloadIdAndStartDate getMetadataDownloadIdAndStartDateForURI(
            final Context context, final String uri) {
        SQLiteDatabase defaultDb = getDb(context, null);
        final Cursor cursor = defaultDb.query(CLIENT_TABLE_NAME,
                new String[] { CLIENT_PENDINGID_COLUMN },
                new String[] { CLIENT_PENDINGID_COLUMN, CLIENT_LAST_UPDATE_DATE_COLUMN },
                CLIENT_METADATA_URI_COLUMN + " = ?", new String[] { uri },
                null, null, null, null);
        try {
            if (!cursor.moveToFirst()) return UpdateHandler.NOT_AN_ID;
            return cursor.getInt(0); // Only one column, return it
            if (!cursor.moveToFirst()) return null;
            return new DownloadIdAndStartDate(cursor.getInt(0), cursor.getLong(1));
        } finally {
            cursor.close();
        }
@@ -922,6 +922,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper {
            final long downloadId) {
        final ContentValues values = new ContentValues();
        values.put(CLIENT_PENDINGID_COLUMN, downloadId);
        values.put(CLIENT_LAST_UPDATE_DATE_COLUMN, System.currentTimeMillis());
        final SQLiteDatabase defaultDb = getDb(context, "");
        final Cursor cursor = MetadataDbHelper.queryClientIds(context);
        if (null == cursor) return;
+24 −11
Original line number Diff line number Diff line
@@ -251,12 +251,16 @@ public final class UpdateHandler {
                res.getBoolean(R.bool.metadata_downloads_visible_in_download_UI));

        final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
        cancelUpdateWithDownloadManager(context, metadataUri, manager);
        if (maybeCancelUpdateAndReturnIfStillRunning(context, metadataUri, manager,
                DictionaryService.NO_CANCEL_DOWNLOAD_PERIOD_MILLIS)) {
            // We already have a recent download in progress. Don't register a new download.
            return;
        }
        final long downloadId;
        synchronized (sSharedIdProtector) {
            downloadId = manager.enqueue(metadataRequest);
            DebugLogUtils.l("Metadata download requested with id", downloadId);
            // If there is already a download in progress, it's been there for a while and
            // If there is still a download in progress, it's been there for a while and
            // there is probably something wrong with download manager. It's best to just
            // overwrite the id and request it again. If the old one happens to finish
            // anyway, we don't know about its ID any more, so the downloadFinished
@@ -267,21 +271,29 @@ public final class UpdateHandler {
    }

    /**
     * Cancels downloading a file, if there is one for this URI.
     * Cancels downloading a file if there is one for this URI and it's too long.
     *
     * If we are not currently downloading the file at this URI, this is a no-op.
     *
     * @param context the context to open the database on
     * @param metadataUri the URI to cancel
     * @param manager an wrapped instance of DownloadManager
     * @param graceTime if there was a download started less than this many milliseconds, don't
     *  cancel and return true
     * @return whether the download is still active
     */
    private static void cancelUpdateWithDownloadManager(final Context context,
            final String metadataUri, final DownloadManagerWrapper manager) {
    private static boolean maybeCancelUpdateAndReturnIfStillRunning(final Context context,
            final String metadataUri, final DownloadManagerWrapper manager, final long graceTime) {
        synchronized (sSharedIdProtector) {
            final long metadataDownloadId =
                    MetadataDbHelper.getMetadataDownloadIdForURI(context, metadataUri);
            if (NOT_AN_ID == metadataDownloadId) return;
            manager.remove(metadataDownloadId);
            final DownloadIdAndStartDate metadataDownloadIdAndStartDate =
                    MetadataDbHelper.getMetadataDownloadIdAndStartDateForURI(context, metadataUri);
            if (null == metadataDownloadIdAndStartDate) return false;
            if (NOT_AN_ID == metadataDownloadIdAndStartDate.mId) return false;
            if (metadataDownloadIdAndStartDate.mStartDate + graceTime
                    > System.currentTimeMillis()) {
                return true;
            }
            manager.remove(metadataDownloadIdAndStartDate.mId);
            writeMetadataDownloadId(context, metadataUri, NOT_AN_ID);
        }
        // Consider a cancellation as a failure. As such, inform listeners that the download
@@ -289,6 +301,7 @@ public final class UpdateHandler {
        for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) {
            listener.downloadedMetadata(false);
        }
        return false;
    }

    /**
@@ -303,7 +316,7 @@ public final class UpdateHandler {
    public static void cancelUpdate(final Context context, final String clientId) {
        final DownloadManagerWrapper manager = new DownloadManagerWrapper(context);
        final String metadataUri = MetadataDbHelper.getMetadataUriAsString(context, clientId);
        cancelUpdateWithDownloadManager(context, metadataUri, manager);
        maybeCancelUpdateAndReturnIfStillRunning(context, metadataUri, manager, 0 /* graceTime */);
    }

    /**
@@ -387,7 +400,7 @@ public final class UpdateHandler {
            // If any of these is metadata, we should update the DB
            boolean hasMetadata = false;
            for (DownloadRecord record : downloadRecords) {
                if (null == record.mAttributes) {
                if (record.isMetadata()) {
                    hasMetadata = true;
                    break;
                }