<commit_before>/*
 * Copyright (C) 2012 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.novoda.downloadmanager.lib;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
import android.support.v4.util.LongSparseArray;
import android.text.TextUtils;
import android.text.format.DateUtils;

import com.novoda.downloadmanager.R;
import com.novoda.notils.logger.simple.Log;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * Update {@link NotificationManager} to reflect current {@link DownloadInfo}
 * states. Collapses similar downloads into a single notification, and builds
 * {@link PendingIntent} that launch towards {DownloadReceiver}.
 */
class DownloadNotifier {

    private static final int TYPE_ACTIVE = 1;
    private static final int TYPE_WAITING = 2;
    private static final int TYPE_SUCCESS = 3;
    private static final int TYPE_FAILED = 4;
    private static final int TYPE_CANCELLED = 5;

    private final Context mContext;
    private final NotificationImageRetriever imageRetriever;
    private final NotificationManager mNotifManager;

    /**
     * Currently active notifications, mapped from clustering tag to timestamp
     * when first shown.
     *
     * @see #buildNotificationTag(DownloadBatch)
     */
//    @GuardedBy("mActiveNotifs")
    private final HashMap<String, Long> mActiveNotifs = new HashMap<>();

    /**
     * Current speed of active downloads, mapped from {@link DownloadInfo#batchId}
     * to speed in bytes per second.
     */
//    @GuardedBy("mDownloadSpeed")
    // LongSparseLongArray
    private final LongSparseArray<Long> mDownloadSpeed = new LongSparseArray<>();

    private final Resources resources;

    public DownloadNotifier(Context context, NotificationImageRetriever imageRetriever, Resources resources) {
        this.mContext = context;
        this.resources = resources;
        this.imageRetriever = imageRetriever;
        this.mNotifManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    }

    public void cancelAll() {
        mNotifManager.cancelAll();
    }

    /**
     * Notify the current speed of an active download, used for calculating
     * estimated remaining time.
     */
    public void notifyDownloadSpeed(long id, long bytesPerSecond) {
        synchronized (mDownloadSpeed) {
            if (bytesPerSecond != 0) {
                mDownloadSpeed.put(id, bytesPerSecond);
            } else {
                mDownloadSpeed.remove(id);
            }
        }
    }

    /**
     * Update {@link NotificationManager} to reflect the given set of
     * {@link DownloadInfo}, adding, collapsing, and removing as needed.
     */
    public void updateWith(Collection<DownloadBatch> batches) {
        synchronized (mActiveNotifs) {
            Map<String, Collection<DownloadBatch>> clusters = getClustersByNotificationTag(batches);

            showNotificationPerCluster(clusters);

            removeStaleTagsThatWereNotRenewed(clusters);
        }
    }

    @NonNull
    private Map<String, Collection<DownloadBatch>> getClustersByNotificationTag(Collection<DownloadBatch> batches) {
        Map<String, Collection<DownloadBatch>> clustered = new HashMap<>();

        for (DownloadBatch batch : batches) {
            String tag = buildNotificationTag(batch);

            addBatchToCluster(tag, clustered, batch);
        }

        return clustered;
    }

    /**
     * Build tag used for collapsing several {@link DownloadInfo} into a single
     * {@link Notification}.
     */
    private String buildNotificationTag(DownloadBatch batch) {
        int status = batch.getStatus();
        int visibility = batch.getInfo().getVisibility();
        if (status == Downloads.Impl.STATUS_QUEUED_FOR_WIFI) {
            return TYPE_WAITING + ":" + mContext.getPackageName();
        } else if (status == Downloads.Impl.STATUS_RUNNING && shouldShowActiveItem(visibility)) {
            return TYPE_ACTIVE + ":" + mContext.getPackageName();
        } else if (Downloads.Impl.isStatusError(status) && !Downloads.Impl.isStatusCancelled(status)
                && shouldShowCompletedItem(visibility)) {
            // Failed downloads always have unique notifs
            return TYPE_FAILED + ":" + batch.getBatchId();
        } else if (Downloads.Impl.isStatusCancelled(status) && shouldShowCompletedItem(visibility)) {
            // Cancelled downloads always have unique notifs
            return TYPE_CANCELLED + ":" + batch.getBatchId();
        } else if (Downloads.Impl.isStatusSuccess(status) && shouldShowCompletedItem(visibility)) {
            // Complete downloads always have unique notifs
            return TYPE_SUCCESS + ":" + batch.getBatchId();
        } else {
            return null;
        }
    }

    private boolean shouldShowActiveItem(int visibility) {
        return visibility == NotificationVisibility.ONLY_WHEN_ACTIVE
                || visibility == NotificationVisibility.ACTIVE_OR_COMPLETE;
    }

    private boolean shouldShowCompletedItem(int visibility) {
        return visibility == NotificationVisibility.ONLY_WHEN_COMPLETE
                || visibility == NotificationVisibility.ACTIVE_OR_COMPLETE;
    }

    private void addBatchToCluster(String tag, Map<String, Collection<DownloadBatch>> cluster, DownloadBatch batch) {
        if (tag == null) {
            return;
        }

        Collection<DownloadBatch> batches;

        if (cluster.containsKey(tag)) {
            batches = cluster.get(tag);
        } else {
            batches = new HashSet<>();
            cluster.put(tag, batches); // TODO not sure if this is right compared to ArrayListMultiMap
        }

        batches.add(batch);
    }

    private void showNotificationPerCluster(Map<String, Collection<DownloadBatch>> clusters) {
        for (String notificationId : clusters.keySet()) {
            int type = getNotificationTagType(notificationId);
            Collection<DownloadBatch> cluster = clusters.get(notificationId);

            NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext);
            useTimeWhenClusterFirstShownToAvoidShuffling(notificationId, builder);
            buildIcon(type, builder);
            buildActionIntents(notificationId, type, cluster, builder);

            Notification notification = buildTitlesAndDescription(type, cluster, builder);
            mNotifManager.notify(notificationId.hashCode(), notification);
        }
    }
<commit_msg>Moves batches from HashSet to ArrayList<commit_after>/*
 * Copyright (C) 2012 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.novoda.downloadmanager.lib;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
import android.support.v4.util.LongSparseArray;
import android.text.TextUtils;
import android.text.format.DateUtils;

import com.novoda.downloadmanager.R;
import com.novoda.notils.logger.simple.Log;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * Update {@link NotificationManager} to reflect current {@link DownloadInfo}
 * states. Collapses similar downloads into a single notification, and builds
 * {@link PendingIntent} that launch towards {DownloadReceiver}.
 */
class DownloadNotifier {

    private static final int TYPE_ACTIVE = 1;
    private static final int TYPE_WAITING = 2;
    private static final int TYPE_SUCCESS = 3;
    private static final int TYPE_FAILED = 4;
    private static final int TYPE_CANCELLED = 5;

    private final Context mContext;
    private final NotificationImageRetriever imageRetriever;
    private final NotificationManager mNotifManager;

    /**
     * Currently active notifications, mapped from clustering tag to timestamp
     * when first shown.
     *
     * @see #buildNotificationTag(DownloadBatch)
     */
//    @GuardedBy("mActiveNotifs")
    private final HashMap<String, Long> mActiveNotifs = new HashMap<>();

    /**
     * Current speed of active downloads, mapped from {@link DownloadInfo#batchId}
     * to speed in bytes per second.
     */
//    @GuardedBy("mDownloadSpeed")
    // LongSparseLongArray
    private final LongSparseArray<Long> mDownloadSpeed = new LongSparseArray<>();

    private final Resources resources;

    public DownloadNotifier(Context context, NotificationImageRetriever imageRetriever, Resources resources) {
        this.mContext = context;
        this.resources = resources;
        this.imageRetriever = imageRetriever;
        this.mNotifManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    }

    public void cancelAll() {
        mNotifManager.cancelAll();
    }

    /**
     * Notify the current speed of an active download, used for calculating
     * estimated remaining time.
     */
    public void notifyDownloadSpeed(long id, long bytesPerSecond) {
        synchronized (mDownloadSpeed) {
            if (bytesPerSecond != 0) {
                mDownloadSpeed.put(id, bytesPerSecond);
            } else {
                mDownloadSpeed.remove(id);
            }
        }
    }

    /**
     * Update {@link NotificationManager} to reflect the given set of
     * {@link DownloadInfo}, adding, collapsing, and removing as needed.
     */
    public void updateWith(Collection<DownloadBatch> batches) {
        synchronized (mActiveNotifs) {
            Map<String, Collection<DownloadBatch>> clusters = getClustersByNotificationTag(batches);

            showNotificationPerCluster(clusters);

            removeStaleTagsThatWereNotRenewed(clusters);
        }
    }

    @NonNull
    private Map<String, Collection<DownloadBatch>> getClustersByNotificationTag(Collection<DownloadBatch> batches) {
        Map<String, Collection<DownloadBatch>> clustered = new HashMap<>();

        for (DownloadBatch batch : batches) {
            String tag = buildNotificationTag(batch);

            addBatchToCluster(tag, clustered, batch);
        }

        return clustered;
    }

    /**
     * Build tag used for collapsing several {@link DownloadInfo} into a single
     * {@link Notification}.
     */
    private String buildNotificationTag(DownloadBatch batch) {
        int status = batch.getStatus();
        int visibility = batch.getInfo().getVisibility();
        if (status == Downloads.Impl.STATUS_QUEUED_FOR_WIFI) {
            return TYPE_WAITING + ":" + mContext.getPackageName();
        } else if (status == Downloads.Impl.STATUS_RUNNING && shouldShowActiveItem(visibility)) {
            return TYPE_ACTIVE + ":" + mContext.getPackageName();
        } else if (Downloads.Impl.isStatusError(status) && !Downloads.Impl.isStatusCancelled(status)
                && shouldShowCompletedItem(visibility)) {
            // Failed downloads always have unique notifs
            return TYPE_FAILED + ":" + batch.getBatchId();
        } else if (Downloads.Impl.isStatusCancelled(status) && shouldShowCompletedItem(visibility)) {
            // Cancelled downloads always have unique notifs
            return TYPE_CANCELLED + ":" + batch.getBatchId();
        } else if (Downloads.Impl.isStatusSuccess(status) && shouldShowCompletedItem(visibility)) {
            // Complete downloads always have unique notifs
            return TYPE_SUCCESS + ":" + batch.getBatchId();
        } else {
            return null;
        }
    }

    private boolean shouldShowActiveItem(int visibility) {
        return visibility == NotificationVisibility.ONLY_WHEN_ACTIVE
                || visibility == NotificationVisibility.ACTIVE_OR_COMPLETE;
    }

    private boolean shouldShowCompletedItem(int visibility) {
        return visibility == NotificationVisibility.ONLY_WHEN_COMPLETE
                || visibility == NotificationVisibility.ACTIVE_OR_COMPLETE;
    }

    private void addBatchToCluster(String tag, Map<String, Collection<DownloadBatch>> cluster, DownloadBatch batch) {
        if (tag == null) {
            return;
        }

        Collection<DownloadBatch> batches;

        if (cluster.containsKey(tag)) {
            batches = cluster.get(tag);
        } else {
            batches = new ArrayList<>();
            cluster.put(tag, batches); // TODO not sure if this is right compared to ArrayListMultiMap
        }

        batches.add(batch);
    }

    private void showNotificationPerCluster(Map<String, Collection<DownloadBatch>> clusters) {
        for (String notificationId : clusters.keySet()) {
            int type = getNotificationTagType(notificationId);
            Collection<DownloadBatch> cluster = clusters.get(notificationId);

            NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext);
            useTimeWhenClusterFirstShownToAvoidShuffling(notificationId, builder);
            buildIcon(type, builder);
            buildActionIntents(notificationId, type, cluster, builder);

            Notification notification = buildTitlesAndDescription(type, cluster, builder);
            mNotifManager.notify(notificationId.hashCode(), notification);
        }
    }

{'fraction_non_alphanumeric': 0.08833289089461109, 'fraction_numerical': 0.002123705866737457, 'mean_word_length': 3.1443894389438944, 'pattern_counts': {'":': 10, '<': 43, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 80, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4335011', 'n_tokens_mistral': 3909, 'n_tokens_neox': 3775, 'n_words': 1049}