--- /dev/null
+/*
+ * Copyright (C) 2017 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.systemui.statusbar;
+
+/**
+ * An interface for running inflation tasks that allows aborting and superseding existing
+ * operations.
+ */
+public interface InflationTask {
+ void abort();
+
+ /**
+ * Supersedes an existing task. i.e another task was superceeded by this.
+ *
+ * @param task the task that was previously running
+ */
+ default void supersedeTask(InflationTask task) {}
+}
import android.content.pm.PackageManager;
import android.content.Context;
import android.graphics.drawable.Icon;
-import android.os.AsyncTask;
import android.os.RemoteException;
import android.os.SystemClock;
import android.service.notification.NotificationListenerService;
public List<SnoozeCriterion> snoozeCriteria;
private int mCachedContrastColor = COLOR_INVALID;
private int mCachedContrastColorIsFor = COLOR_INVALID;
- private Abortable mRunningTask = null;
+ private InflationTask mRunningTask = null;
public Entry(StatusBarNotification n) {
this.key = n.getKey();
}
}
- public void setInflationTask(Abortable abortableTask) {
+ public void setInflationTask(InflationTask abortableTask) {
// abort any existing inflation
+ InflationTask existing = mRunningTask;
abortTask();
mRunningTask = abortableTask;
+ if (existing != null && mRunningTask != null) {
+ mRunningTask.supersedeTask(existing);
+ }
}
public void onInflationTaskFinished() {
}
@VisibleForTesting
- public Abortable getRunningTask() {
+ public InflationTask getRunningTask() {
return mRunningTask;
}
}
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
-import com.android.systemui.statusbar.Abortable;
+import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationContentView;
import com.android.systemui.statusbar.NotificationData;
*/
@VisibleForTesting
void inflateNotificationViews(int reInflateFlags) {
+ if (mRow.isRemoved()) {
+ // We don't want to reinflate anything for removed notifications. Otherwise views might
+ // be readded to the stack, leading to leaks. This may happen with low-priority groups
+ // where the removal of already removed children can lead to a reinflation.
+ return;
+ }
StatusBarNotification sbn = mRow.getEntry().notification;
new AsyncInflationTask(sbn, reInflateFlags, mRow, mIsLowPriority,
mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient,
}
public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress>
- implements InflationCallback, Abortable {
+ implements InflationCallback, InflationTask {
private final StatusBarNotification mSbn;
private final Context mContext;
- private final int mReInflateFlags;
private final boolean mIsLowPriority;
private final boolean mIsChildInGroup;
private final boolean mUsesIncreasedHeight;
private final InflationCallback mCallback;
private final boolean mUsesIncreasedHeadsUpHeight;
private final boolean mRedactAmbient;
+ private int mReInflateFlags;
private ExpandableNotificationRow mRow;
private Exception mError;
private RemoteViews.OnClickHandler mRemoteViewClickHandler;
InflationCallback callback,
RemoteViews.OnClickHandler remoteViewClickHandler) {
mRow = row;
- NotificationData.Entry entry = row.getEntry();
- entry.setInflationTask(this);
mSbn = notification;
mReInflateFlags = reInflateFlags;
mContext = mRow.getContext();
mRedactAmbient = redactAmbient;
mRemoteViewClickHandler = remoteViewClickHandler;
mCallback = callback;
+ NotificationData.Entry entry = row.getEntry();
+ entry.setInflationTask(this);
+ }
+
+ @VisibleForTesting
+ public int getReInflateFlags() {
+ return mReInflateFlags;
}
@Override
}
@Override
+ public void supersedeTask(InflationTask task) {
+ if (task instanceof AsyncInflationTask) {
+ // We want to inflate all flags of the previous task as well
+ mReInflateFlags |= ((AsyncInflationTask) task).mReInflateFlags;
+ }
+ }
+
+ @Override
public void handleInflationException(StatusBarNotification notification, Exception e) {
handleError(e);
}
import android.view.ViewGroup;
import com.android.systemui.R;
-import com.android.systemui.statusbar.Abortable;
+import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationData;
/**
* An inflater task that asynchronously inflates a ExpandableNotificationRow
*/
-public class RowInflaterTask implements Abortable, AsyncLayoutInflater.OnInflateFinishedListener {
+public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInflateFinishedListener {
private RowInflationFinishedListener mListener;
private NotificationData.Entry mEntry;
private boolean mCancelled;
@Override
public void onAsyncInflationFinished(Entry entry) {
mPendingNotifications.remove(entry.key);
- if (mNotificationData.get(entry.key) == null) {
+ // If there was an async task started after the removal, we don't want to add it back to
+ // the list, otherwise we might get leaks.
+ if (mNotificationData.get(entry.key) == null && !entry.row.isRemoved()) {
addEntry(entry);
}
}
continue;
}
toRemove.add(row);
- toRemove.get(i).setKeepInParent(true);
+ row.setKeepInParent(true);
// we need to set this state earlier as otherwise we might generate some weird
// animations
- toRemove.get(i).setRemoved();
+ row.setRemoved();
}
}
}
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationTestHelper;
+import com.android.systemui.statusbar.InflationTask;
import org.junit.Assert;
import org.junit.Before;
mRow.getEntry().abortTask();
runThenWaitForInflation(() -> mNotificationInflater.inflateNotificationViews(),
mNotificationInflater);
- Assert.assertNull(mRow.getEntry().getRunningTask() );
+ verify(mRow).onNotificationUpdated();
+ }
+
+ @Test
+ public void testRemovedNotInflated() throws Exception {
+ mRow.setRemoved();
+ mNotificationInflater.inflateNotificationViews();
+ Assert.assertNull(mRow.getEntry().getRunningTask());
+ }
+
+
+ @Test
+ public void testSupersedesExistingTask() throws Exception {
+ mNotificationInflater.inflateNotificationViews();
+ mNotificationInflater.setIsLowPriority(true);
+ mNotificationInflater.setIsChildInGroup(true);
+ InflationTask runningTask = mRow.getEntry().getRunningTask();
+ NotificationInflater.AsyncInflationTask asyncInflationTask =
+ (NotificationInflater.AsyncInflationTask) runningTask;
+ Assert.assertSame("Successive inflations don't inherit the previous flags!",
+ asyncInflationTask.getReInflateFlags(),
+ NotificationInflater.FLAG_REINFLATE_ALL);
+ runningTask.abort();
}
public static void runThenWaitForInflation(Runnable block,