/* * Copyright (C) 2015 AChep@xda <artemchep@gmail.com> * * This program 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 2 * of the License, or (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ package com.achep.acdisplay.notifications; import android.content.Context; import android.graphics.Bitmap; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; import com.achep.base.tests.Check; import java.util.concurrent.ConcurrentLinkedQueue; import static com.achep.acdisplay.graphics.IconFactory.generate; import static com.achep.base.Build.DEBUG; /** * Simple single-thread icon factory. * * @author Artem Chepurnoy */ public class IconFactory { private static final String TAG = "IconFactory"; public interface IconAsyncListener { void onGenerated(@NonNull Bitmap bitmap); } /** * @author Artem Chepurnoy */ private static final class Worker extends Thread { private static class Task { @NonNull private Context context; @NonNull private IconAsyncListener listener; @NonNull private OpenNotification notification; public Task(@NonNull Context context, @NonNull IconAsyncListener listener, @NonNull OpenNotification notification) { this.context = context; this.listener = listener; this.notification = notification; } } private final ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<>(); private final IconFactory mFactory; private final Object mMonitor; private volatile boolean mStopping; Worker(IconFactory factory, @NonNull Object monitor) { mFactory = factory; mMonitor = monitor; } @Override public void run() { super.run(); final long start = SystemClock.elapsedRealtime(); int n = 0; while (true) { // Get the next task from // queue. final Task task; synchronized (mMonitor) { if (mQueue.isEmpty()) { mStopping = true; if (DEBUG) { long delta = SystemClock.elapsedRealtime() - start; Log.d(TAG, "Done loading icons: " + " delta=" + delta + "ms." + " count=" + n); } return; } task = mQueue.poll(); assert task != null; } final Bitmap bitmap = mFactory.onGenerate(task.context, task.notification); mFactory.handler.post(new Runnable() { @Override public void run() { Check.getInstance().isInMainThread(); task.listener.onGenerated(bitmap); } }); n++; } } void add(@NonNull Context context, @NonNull OpenNotification notification, @NonNull IconAsyncListener listener) { Task task = new Task(context, listener, notification); Check.getInstance().isFalse(mStopping); mQueue.add(task); } void remove(@NonNull OpenNotification notification) { Check.getInstance().isFalse(mStopping); for (Task task : mQueue) { if (task.notification == notification) { mQueue.remove(task); return; } } } /** * @return {@code true} if the current worker may accept and handle any * of incoming tasks, {@code false} otherwise. */ boolean isActive() { return !mStopping; } } @Nullable private Worker mWorker; @NonNull private Handler handler = new Handler(Looper.getMainLooper()); @NonNull private final Object mMonitor = new Object(); @NonNull protected Bitmap onGenerate(@NonNull Context context, @NonNull OpenNotification notification) { return generate(context, notification); } /** * Adds the notification to the tasks list. * * @param notification a notification to load from * @param listener a callback * @see #remove(OpenNotification) */ public void add(@NonNull Context context, @NonNull OpenNotification notification, @NonNull IconAsyncListener listener) { synchronized (mMonitor) { boolean create = isWorkerInactive(); if (create) { mWorker = new Worker(this, mMonitor); mWorker.setPriority(Thread.MAX_PRIORITY); } assert mWorker != null; mWorker.add(context, notification, listener); if (create) mWorker.start(); } } /** * Removes the notification from the task list (if available). * * @see #add(android.content.Context, OpenNotification, IconFactory.IconAsyncListener) */ public void remove(@NonNull OpenNotification notification) { synchronized (mMonitor) { if (isWorkerInactive()) { // It's too late :( return; } assert mWorker != null; mWorker.remove(notification); } } private boolean isWorkerInactive() { return mWorker == null || !mWorker.isActive(); } }