Merge changes from topic "notiflistbuilder"
* changes: Add NotifListBuilder to new notif pipeline Add Dagger-injectable version of SystemClock
This commit is contained in:
@@ -96,12 +96,12 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController;
|
||||
import com.android.systemui.statusbar.VibratorHelper;
|
||||
import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
|
||||
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
|
||||
import com.android.systemui.statusbar.notification.NewNotifPipeline;
|
||||
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
|
||||
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
|
||||
import com.android.systemui.statusbar.notification.VisualStabilityManager;
|
||||
import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
|
||||
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
|
||||
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
|
||||
import com.android.systemui.statusbar.phone.AutoHideController;
|
||||
|
||||
@@ -56,12 +56,12 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController;
|
||||
import com.android.systemui.statusbar.VibratorHelper;
|
||||
import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
|
||||
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
|
||||
import com.android.systemui.statusbar.notification.NewNotifPipeline;
|
||||
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
|
||||
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
|
||||
import com.android.systemui.statusbar.notification.VisualStabilityManager;
|
||||
import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
|
||||
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
|
||||
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
|
||||
import com.android.systemui.statusbar.phone.AutoHideController;
|
||||
|
||||
@@ -28,10 +28,14 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.recents.Recents;
|
||||
import com.android.systemui.stackdivider.Divider;
|
||||
import com.android.systemui.statusbar.CommandQueue;
|
||||
import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
|
||||
import com.android.systemui.statusbar.notification.people.PeopleHubModule;
|
||||
import com.android.systemui.statusbar.phone.KeyguardLiftController;
|
||||
import com.android.systemui.statusbar.phone.StatusBar;
|
||||
import com.android.systemui.util.sensors.AsyncSensorManager;
|
||||
import com.android.systemui.util.time.SystemClock;
|
||||
import com.android.systemui.util.time.SystemClockImpl;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@@ -84,4 +88,12 @@ public abstract class SystemUIModule {
|
||||
|
||||
@BindsOptionalOf
|
||||
abstract StatusBar optionalStatusBar();
|
||||
|
||||
@Singleton
|
||||
@Binds
|
||||
abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
|
||||
|
||||
@Singleton
|
||||
@Binds
|
||||
abstract NotifListBuilder bindNotifListBuilder(NotifListBuilderImpl impl);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import java.util.Collection;
|
||||
* Interface for the class responsible for converting a NotifCollection into the final sorted,
|
||||
* filtered, and grouped list of currently visible notifications.
|
||||
*/
|
||||
public interface NotifListBuilder {
|
||||
public interface CollectionReadyForBuildListener {
|
||||
/**
|
||||
* Called after the NotifCollection has received an update from NotificationManager but before
|
||||
* it dispatches any change events to its listeners. This is to inform the list builder that
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.notification.collection;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a set of grouped notifications. The final notification list is usually a mix of
|
||||
* GroupEntries and NotificationEntries.
|
||||
*/
|
||||
public class GroupEntry extends ListEntry {
|
||||
@Nullable private NotificationEntry mSummary;
|
||||
private final List<NotificationEntry> mChildren = new ArrayList<>();
|
||||
|
||||
private final List<NotificationEntry> mUnmodifiableChildren =
|
||||
Collections.unmodifiableList(mChildren);
|
||||
|
||||
GroupEntry(String key) {
|
||||
super(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NotificationEntry getRepresentativeEntry() {
|
||||
return mSummary;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public NotificationEntry getSummary() {
|
||||
return mSummary;
|
||||
}
|
||||
|
||||
public List<NotificationEntry> getChildren() {
|
||||
return mUnmodifiableChildren;
|
||||
}
|
||||
|
||||
void setSummary(@Nullable NotificationEntry summary) {
|
||||
mSummary = summary;
|
||||
}
|
||||
|
||||
void clearChildren() {
|
||||
mChildren.clear();
|
||||
}
|
||||
|
||||
void addChild(NotificationEntry child) {
|
||||
mChildren.add(child);
|
||||
}
|
||||
|
||||
void sortChildren(Comparator<? super NotificationEntry> c) {
|
||||
mChildren.sort(c);
|
||||
}
|
||||
|
||||
List<NotificationEntry> getRawChildren() {
|
||||
return mChildren;
|
||||
}
|
||||
|
||||
public static final GroupEntry ROOT_ENTRY = new GroupEntry("<root>");
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.notification.collection;
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Utility class for dumping the results of a {@link NotifListBuilder} to a debug string.
|
||||
*/
|
||||
public class ListDumper {
|
||||
|
||||
/** See class description */
|
||||
public static String dumpList(List<ListEntry> entries) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
ListEntry entry = entries.get(i);
|
||||
dumpEntry(entry, Integer.toString(i), "", sb);
|
||||
if (entry instanceof GroupEntry) {
|
||||
GroupEntry ge = (GroupEntry) entry;
|
||||
for (int j = 0; j < ge.getChildren().size(); j++) {
|
||||
dumpEntry(
|
||||
ge.getChildren().get(j),
|
||||
Integer.toString(j),
|
||||
INDENT,
|
||||
sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void dumpEntry(
|
||||
ListEntry entry, String index, String indent, StringBuilder sb) {
|
||||
sb.append(indent)
|
||||
.append("[").append(index).append("] ")
|
||||
.append(entry.getKey())
|
||||
.append(" (parent=")
|
||||
.append(entry.getParent() != null ? entry.getParent().getKey() : null)
|
||||
.append(")\n");
|
||||
}
|
||||
|
||||
private static final String INDENT = " ";
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.notification.collection;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Abstract superclass for top-level entries, i.e. things that can appear in the final notification
|
||||
* list shown to users. In practice, this means either GroupEntries or NotificationEntries.
|
||||
*/
|
||||
public abstract class ListEntry {
|
||||
private final String mKey;
|
||||
|
||||
@Nullable private GroupEntry mParent;
|
||||
@Nullable private GroupEntry mPreviousParent;
|
||||
private int mSection;
|
||||
int mFirstAddedIteration = -1;
|
||||
|
||||
ListEntry(String key) {
|
||||
mKey = key;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return mKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should return the "representative entry" for this ListEntry. For NotificationEntries, its
|
||||
* the entry itself. For groups, it should be the summary. This method exists to interface with
|
||||
* legacy code that expects groups to also be NotificationEntries.
|
||||
*/
|
||||
public abstract NotificationEntry getRepresentativeEntry();
|
||||
|
||||
@Nullable public GroupEntry getParent() {
|
||||
return mParent;
|
||||
}
|
||||
|
||||
void setParent(@Nullable GroupEntry parent) {
|
||||
mParent = parent;
|
||||
}
|
||||
|
||||
@Nullable public GroupEntry getPreviousParent() {
|
||||
return mPreviousParent;
|
||||
}
|
||||
|
||||
void setPreviousParent(@Nullable GroupEntry previousParent) {
|
||||
mPreviousParent = previousParent;
|
||||
}
|
||||
|
||||
/** The section this notification was assigned to (0 to N-1, where N is number of sections). */
|
||||
public int getSection() {
|
||||
return mSection;
|
||||
}
|
||||
|
||||
void setSection(int section) {
|
||||
mSection = section;
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ public class NotifCollection {
|
||||
private final Collection<NotificationEntry> mReadOnlyNotificationSet =
|
||||
Collections.unmodifiableCollection(mNotificationSet.values());
|
||||
|
||||
@Nullable private NotifListBuilder mListBuilder;
|
||||
@Nullable private CollectionReadyForBuildListener mBuildListener;
|
||||
private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>();
|
||||
private final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>();
|
||||
|
||||
@@ -123,9 +123,9 @@ public class NotifCollection {
|
||||
* Sets the class responsible for converting the collection into the list of currently-visible
|
||||
* notifications.
|
||||
*/
|
||||
public void setListBuilder(NotifListBuilder listBuilder) {
|
||||
public void setBuildListener(CollectionReadyForBuildListener buildListener) {
|
||||
Assert.isMainThread();
|
||||
mListBuilder = listBuilder;
|
||||
mBuildListener = buildListener;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -282,8 +282,8 @@ public class NotifCollection {
|
||||
}
|
||||
|
||||
private void rebuildList() {
|
||||
if (mListBuilder != null) {
|
||||
mListBuilder.onBuildList(mReadOnlyNotificationSet);
|
||||
if (mBuildListener != null) {
|
||||
mBuildListener.onBuildList(mReadOnlyNotificationSet);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,8 +339,8 @@ public class NotifCollection {
|
||||
|
||||
private void dispatchOnEntryAdded(NotificationEntry entry) {
|
||||
mAmDispatchingToOtherCode = true;
|
||||
if (mListBuilder != null) {
|
||||
mListBuilder.onBeginDispatchToListeners();
|
||||
if (mBuildListener != null) {
|
||||
mBuildListener.onBeginDispatchToListeners();
|
||||
}
|
||||
for (NotifCollectionListener listener : mNotifCollectionListeners) {
|
||||
listener.onEntryAdded(entry);
|
||||
@@ -350,8 +350,8 @@ public class NotifCollection {
|
||||
|
||||
private void dispatchOnEntryUpdated(NotificationEntry entry) {
|
||||
mAmDispatchingToOtherCode = true;
|
||||
if (mListBuilder != null) {
|
||||
mListBuilder.onBeginDispatchToListeners();
|
||||
if (mBuildListener != null) {
|
||||
mBuildListener.onBeginDispatchToListeners();
|
||||
}
|
||||
for (NotifCollectionListener listener : mNotifCollectionListeners) {
|
||||
listener.onEntryUpdated(entry);
|
||||
@@ -364,8 +364,8 @@ public class NotifCollection {
|
||||
@CancellationReason int reason,
|
||||
boolean removedByUser) {
|
||||
mAmDispatchingToOtherCode = true;
|
||||
if (mListBuilder != null) {
|
||||
mListBuilder.onBeginDispatchToListeners();
|
||||
if (mBuildListener != null) {
|
||||
mBuildListener.onBeginDispatchToListeners();
|
||||
}
|
||||
for (NotifCollectionListener listener : mNotifCollectionListeners) {
|
||||
listener.onEntryRemoved(entry, reason, removedByUser);
|
||||
|
||||
@@ -0,0 +1,737 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.notification.collection;
|
||||
|
||||
import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY;
|
||||
import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpList;
|
||||
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_BUILD_PENDING;
|
||||
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_BUILD_STARTED;
|
||||
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FILTERING;
|
||||
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FINALIZING;
|
||||
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_IDLE;
|
||||
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_SORTING;
|
||||
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_TRANSFORMING;
|
||||
|
||||
import android.annotation.MainThread;
|
||||
import android.annotation.Nullable;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider;
|
||||
import com.android.systemui.util.Assert;
|
||||
import com.android.systemui.util.time.SystemClock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* The implementation of {@link NotifListBuilder}.
|
||||
*/
|
||||
@MainThread
|
||||
@Singleton
|
||||
public class NotifListBuilderImpl implements NotifListBuilder {
|
||||
|
||||
private final SystemClock mSystemClock;
|
||||
|
||||
private final List<ListEntry> mNotifList = new ArrayList<>();
|
||||
|
||||
private final PipelineState mPipelineState = new PipelineState();
|
||||
private final Map<String, GroupEntry> mGroups = new ArrayMap<>();
|
||||
private Collection<NotificationEntry> mAllEntries = Collections.emptyList();
|
||||
private final List<ListEntry> mNewEntries = new ArrayList<>();
|
||||
private int mIterationCount = 0;
|
||||
|
||||
private final List<NotifFilter> mNotifFilters = new ArrayList<>();
|
||||
private final List<NotifPromoter> mNotifPromoters = new ArrayList<>();
|
||||
private final List<NotifComparator> mNotifComparators = new ArrayList<>();
|
||||
private SectionsProvider mSectionsProvider = new DefaultSectionsProvider();
|
||||
|
||||
private final List<OnBeforeTransformGroupsListener> mOnBeforeTransformGroupsListeners =
|
||||
new ArrayList<>();
|
||||
private final List<OnBeforeSortListener> mOnBeforeSortListeners =
|
||||
new ArrayList<>();
|
||||
private final List<OnBeforeRenderListListener> mOnBeforeRenderListListeners =
|
||||
new ArrayList<>();
|
||||
@Nullable private OnRenderListListener mOnRenderListListener;
|
||||
|
||||
private final List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList);
|
||||
|
||||
@Inject
|
||||
public NotifListBuilderImpl(SystemClock systemClock) {
|
||||
Assert.isMainThread();
|
||||
mSystemClock = systemClock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach the list builder to the NotifCollection. After this is called, it will start building
|
||||
* the notif list in response to changes to the colletion.
|
||||
*/
|
||||
public void attach(NotifCollection collection) {
|
||||
Assert.isMainThread();
|
||||
collection.setBuildListener(mReadyForBuildListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener that's responsible for rendering the notif list to the screen. Called
|
||||
* At the very end of pipeline execution, after all other listeners and pluggables have fired.
|
||||
*/
|
||||
public void setOnRenderListListener(OnRenderListListener onRenderListListener) {
|
||||
Assert.isMainThread();
|
||||
|
||||
mPipelineState.requireState(STATE_IDLE);
|
||||
mOnRenderListListener = onRenderListListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener) {
|
||||
Assert.isMainThread();
|
||||
|
||||
mPipelineState.requireState(STATE_IDLE);
|
||||
mOnBeforeTransformGroupsListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOnBeforeSortListener(OnBeforeSortListener listener) {
|
||||
Assert.isMainThread();
|
||||
|
||||
mPipelineState.requireState(STATE_IDLE);
|
||||
mOnBeforeSortListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) {
|
||||
Assert.isMainThread();
|
||||
|
||||
mPipelineState.requireState(STATE_IDLE);
|
||||
mOnBeforeRenderListListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFilter(NotifFilter filter) {
|
||||
Assert.isMainThread();
|
||||
mPipelineState.requireState(STATE_IDLE);
|
||||
|
||||
mNotifFilters.add(filter);
|
||||
filter.setInvalidationListener(this::onFilterInvalidated);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPromoter(NotifPromoter promoter) {
|
||||
Assert.isMainThread();
|
||||
mPipelineState.requireState(STATE_IDLE);
|
||||
|
||||
mNotifPromoters.add(promoter);
|
||||
promoter.setInvalidationListener(this::onPromoterInvalidated);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSectionsProvider(SectionsProvider provider) {
|
||||
Assert.isMainThread();
|
||||
mPipelineState.requireState(STATE_IDLE);
|
||||
|
||||
mSectionsProvider = provider;
|
||||
provider.setInvalidationListener(this::onSectionsProviderInvalidated);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setComparators(List<NotifComparator> comparators) {
|
||||
Assert.isMainThread();
|
||||
mPipelineState.requireState(STATE_IDLE);
|
||||
|
||||
mNotifComparators.clear();
|
||||
for (NotifComparator comparator : comparators) {
|
||||
mNotifComparators.add(comparator);
|
||||
comparator.setInvalidationListener(this::onNotifComparatorInvalidated);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ListEntry> getActiveNotifs() {
|
||||
Assert.isMainThread();
|
||||
return mReadOnlyNotifList;
|
||||
}
|
||||
|
||||
private final CollectionReadyForBuildListener mReadyForBuildListener =
|
||||
new CollectionReadyForBuildListener() {
|
||||
@Override
|
||||
public void onBeginDispatchToListeners() {
|
||||
Assert.isMainThread();
|
||||
mPipelineState.incrementTo(STATE_BUILD_PENDING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuildList(Collection<NotificationEntry> entries) {
|
||||
Assert.isMainThread();
|
||||
mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
|
||||
|
||||
Log.i(TAG, "Build request received from NotifCollection");
|
||||
mAllEntries = entries;
|
||||
buildList();
|
||||
}
|
||||
};
|
||||
|
||||
private void onFilterInvalidated(NotifFilter filter) {
|
||||
Assert.isMainThread();
|
||||
|
||||
// TODO: Convert these log statements (here and elsewhere) into timeline logging
|
||||
Log.i(TAG, String.format(
|
||||
"Filter \"%s\" invalidated; pipeline state is %d",
|
||||
filter.getName(),
|
||||
mPipelineState.getState()));
|
||||
|
||||
rebuildListIfBefore(STATE_FILTERING);
|
||||
}
|
||||
|
||||
private void onPromoterInvalidated(NotifPromoter filter) {
|
||||
Assert.isMainThread();
|
||||
|
||||
Log.i(TAG, String.format(
|
||||
"NotifPromoter \"%s\" invalidated; pipeline state is %d",
|
||||
filter.getName(),
|
||||
mPipelineState.getState()));
|
||||
|
||||
rebuildListIfBefore(STATE_TRANSFORMING);
|
||||
}
|
||||
|
||||
private void onSectionsProviderInvalidated(SectionsProvider provider) {
|
||||
Assert.isMainThread();
|
||||
|
||||
Log.i(TAG, String.format(
|
||||
"Sections provider \"%s\" invalidated; pipeline state is %d",
|
||||
provider.getName(),
|
||||
mPipelineState.getState()));
|
||||
|
||||
rebuildListIfBefore(STATE_SORTING);
|
||||
}
|
||||
|
||||
private void onNotifComparatorInvalidated(NotifComparator comparator) {
|
||||
Assert.isMainThread();
|
||||
|
||||
Log.i(TAG, String.format(
|
||||
"Comparator \"%s\" invalidated; pipeline state is %d",
|
||||
comparator.getName(),
|
||||
mPipelineState.getState()));
|
||||
|
||||
rebuildListIfBefore(STATE_SORTING);
|
||||
}
|
||||
|
||||
/**
|
||||
* The core algorithm of the pipeline. See the top comment in {@link NotifListBuilder} for
|
||||
* details on our contracts with other code.
|
||||
*
|
||||
* Once the build starts we are very careful to protect against reentrant code. Anything that
|
||||
* tries to invalidate itself after the pipeline has passed it by will return in an exception.
|
||||
* In general, we should be extremely sensitive to client code doing things in the wrong order;
|
||||
* if we detect that behavior, we should crash instantly.
|
||||
*/
|
||||
private void buildList() {
|
||||
Log.i(TAG, "Starting notif list build #" + mIterationCount + "...");
|
||||
|
||||
mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
|
||||
mPipelineState.setState(STATE_BUILD_STARTED);
|
||||
|
||||
// Step 1: Filtering and initial grouping
|
||||
// Filter out any notifs that shouldn't be shown right now and cluster any that are part of
|
||||
// a group
|
||||
mPipelineState.incrementTo(STATE_FILTERING);
|
||||
mNotifList.clear();
|
||||
mNewEntries.clear();
|
||||
filterAndGroup(mAllEntries, mNotifList, mNewEntries);
|
||||
pruneIncompleteGroups(mNotifList, mNewEntries);
|
||||
|
||||
// Step 2: Group transforming
|
||||
// Move some notifs out of their groups and up to top-level (mostly used for heads-upping)
|
||||
dispatchOnBeforeTransformGroups(mReadOnlyNotifList, mNewEntries);
|
||||
mPipelineState.incrementTo(STATE_TRANSFORMING);
|
||||
promoteNotifs(mNotifList);
|
||||
pruneIncompleteGroups(mNotifList, mNewEntries);
|
||||
|
||||
// Step 3: Sort
|
||||
// Assign each top-level entry a section, then sort the list by section and then within
|
||||
// section by our list of custom comparators
|
||||
dispatchOnBeforeSort(mReadOnlyNotifList);
|
||||
mPipelineState.incrementTo(STATE_SORTING);
|
||||
sortList();
|
||||
|
||||
// Step 4: Lock in our group structure and log anything that's changed since the last run
|
||||
mPipelineState.incrementTo(STATE_FINALIZING);
|
||||
logParentingChanges();
|
||||
freeEmptyGroups();
|
||||
|
||||
// Step 5: Dispatch the new list, first to any listeners and then to the view layer
|
||||
Log.i(TAG, "List finalized, is:\n" + dumpList(mNotifList));
|
||||
Log.i(TAG, "Dispatching final list to listeners...");
|
||||
dispatchOnBeforeRenderList(mReadOnlyNotifList);
|
||||
if (mOnRenderListListener != null) {
|
||||
mOnRenderListListener.onRenderList(mReadOnlyNotifList);
|
||||
}
|
||||
|
||||
// Step 6: We're done!
|
||||
Log.i(TAG, "Notif list build #" + mIterationCount + " completed");
|
||||
mPipelineState.setState(STATE_IDLE);
|
||||
mIterationCount++;
|
||||
}
|
||||
|
||||
private void filterAndGroup(
|
||||
Collection<NotificationEntry> entries,
|
||||
List<ListEntry> out,
|
||||
List<ListEntry> newlyVisibleEntries) {
|
||||
|
||||
long now = mSystemClock.uptimeMillis();
|
||||
|
||||
for (GroupEntry group : mGroups.values()) {
|
||||
group.setPreviousParent(group.getParent());
|
||||
group.setParent(null);
|
||||
group.clearChildren();
|
||||
group.setSummary(null);
|
||||
}
|
||||
|
||||
for (NotificationEntry entry : entries) {
|
||||
entry.setPreviousParent(entry.getParent());
|
||||
entry.setParent(null);
|
||||
|
||||
// See if we should filter out this notification
|
||||
boolean shouldFilterOut = applyFilters(entry, now);
|
||||
if (shouldFilterOut) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.mFirstAddedIteration == -1) {
|
||||
entry.mFirstAddedIteration = mIterationCount;
|
||||
newlyVisibleEntries.add(entry);
|
||||
}
|
||||
|
||||
// Otherwise, group it
|
||||
if (entry.getSbn().isGroup()) {
|
||||
final String topLevelKey = entry.getSbn().getGroupKey();
|
||||
|
||||
GroupEntry group = mGroups.get(topLevelKey);
|
||||
if (group == null) {
|
||||
group = new GroupEntry(topLevelKey);
|
||||
group.mFirstAddedIteration = mIterationCount;
|
||||
newlyVisibleEntries.add(group);
|
||||
mGroups.put(topLevelKey, group);
|
||||
}
|
||||
if (group.getParent() == null) {
|
||||
group.setParent(ROOT_ENTRY);
|
||||
out.add(group);
|
||||
}
|
||||
|
||||
entry.setParent(group);
|
||||
|
||||
if (entry.getSbn().getNotification().isGroupSummary()) {
|
||||
final NotificationEntry existingSummary = group.getSummary();
|
||||
|
||||
if (existingSummary == null) {
|
||||
group.setSummary(entry);
|
||||
} else {
|
||||
Log.w(TAG, String.format(
|
||||
"Duplicate summary for group '%s': '%s' vs. '%s'",
|
||||
group.getKey(),
|
||||
existingSummary.getKey(),
|
||||
entry.getKey()));
|
||||
|
||||
// Use whichever one was posted most recently
|
||||
if (entry.getSbn().getPostTime()
|
||||
> existingSummary.getSbn().getPostTime()) {
|
||||
group.setSummary(entry);
|
||||
annulAddition(existingSummary, out, newlyVisibleEntries);
|
||||
} else {
|
||||
annulAddition(entry, out, newlyVisibleEntries);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
group.addChild(entry);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
final String topLevelKey = entry.getKey();
|
||||
if (mGroups.containsKey(topLevelKey)) {
|
||||
Log.wtf(TAG, "Duplicate non-group top-level key: " + topLevelKey);
|
||||
} else {
|
||||
entry.setParent(ROOT_ENTRY);
|
||||
out.add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void promoteNotifs(List<ListEntry> list) {
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
final ListEntry tle = list.get(i);
|
||||
|
||||
if (tle instanceof GroupEntry) {
|
||||
final GroupEntry group = (GroupEntry) tle;
|
||||
|
||||
group.getRawChildren().removeIf(child -> {
|
||||
final boolean shouldPromote = applyTopLevelPromoters(child);
|
||||
|
||||
if (shouldPromote) {
|
||||
child.setParent(ROOT_ENTRY);
|
||||
list.add(child);
|
||||
}
|
||||
|
||||
return shouldPromote;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void pruneIncompleteGroups(
|
||||
List<ListEntry> shadeList,
|
||||
List<ListEntry> newlyVisibleEntries) {
|
||||
|
||||
for (int i = 0; i < shadeList.size(); i++) {
|
||||
final ListEntry tle = shadeList.get(i);
|
||||
|
||||
if (tle instanceof GroupEntry) {
|
||||
final GroupEntry group = (GroupEntry) tle;
|
||||
final List<NotificationEntry> children = group.getRawChildren();
|
||||
|
||||
if (group.getSummary() != null && children.size() == 0) {
|
||||
shadeList.remove(i);
|
||||
i--;
|
||||
|
||||
NotificationEntry summary = group.getSummary();
|
||||
summary.setParent(ROOT_ENTRY);
|
||||
shadeList.add(summary);
|
||||
|
||||
group.setSummary(null);
|
||||
annulAddition(group, shadeList, newlyVisibleEntries);
|
||||
|
||||
} else if (group.getSummary() == null
|
||||
|| children.size() < MIN_CHILDREN_FOR_GROUP) {
|
||||
// If the group doesn't provide a summary or is too small, ignore it and add
|
||||
// its children (if any) directly to top-level.
|
||||
|
||||
shadeList.remove(i);
|
||||
i--;
|
||||
|
||||
if (group.getSummary() != null) {
|
||||
final NotificationEntry summary = group.getSummary();
|
||||
group.setSummary(null);
|
||||
annulAddition(summary, shadeList, newlyVisibleEntries);
|
||||
}
|
||||
|
||||
for (int j = 0; j < children.size(); j++) {
|
||||
final NotificationEntry child = children.get(j);
|
||||
child.setParent(ROOT_ENTRY);
|
||||
shadeList.add(child);
|
||||
}
|
||||
children.clear();
|
||||
|
||||
annulAddition(group, shadeList, newlyVisibleEntries);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a ListEntry was added to the shade list and then later removed (e.g. because it was a
|
||||
* group that was broken up), this method will erase any bookkeeping traces of that addition
|
||||
* and/or check that they were already erased.
|
||||
*
|
||||
* Before calling this method, the entry must already have been removed from its parent. If
|
||||
* it's a group, its summary must be null and its children must be empty.
|
||||
*/
|
||||
private void annulAddition(
|
||||
ListEntry entry,
|
||||
List<ListEntry> shadeList,
|
||||
List<ListEntry> newlyVisibleEntries) {
|
||||
|
||||
// This function does very little, but if any of its assumptions are violated (and it has a
|
||||
// lot of them), it will put the system into an inconsistent state. So we check all of them
|
||||
// here.
|
||||
|
||||
if (entry.getParent() == null || entry.mFirstAddedIteration == -1) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot nullify addition of " + entry.getKey() + ": no such addition. ("
|
||||
+ entry.getParent() + " " + entry.mFirstAddedIteration + ")");
|
||||
}
|
||||
|
||||
if (entry.getParent() == ROOT_ENTRY) {
|
||||
if (shadeList.contains(entry)) {
|
||||
throw new IllegalStateException("Cannot nullify addition of " + entry.getKey()
|
||||
+ ": it's still in the shade list.");
|
||||
}
|
||||
}
|
||||
|
||||
if (entry instanceof GroupEntry) {
|
||||
GroupEntry ge = (GroupEntry) entry;
|
||||
if (ge.getSummary() != null) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot nullify group " + ge.getKey() + ": summary is not null");
|
||||
}
|
||||
if (!ge.getChildren().isEmpty()) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot nullify group " + ge.getKey() + ": still has children");
|
||||
}
|
||||
} else if (entry instanceof NotificationEntry) {
|
||||
if (entry == entry.getParent().getSummary()
|
||||
|| entry.getParent().getChildren().contains(entry)) {
|
||||
throw new IllegalStateException("Cannot nullify addition of child "
|
||||
+ entry.getKey() + ": it's still attached to its parent.");
|
||||
}
|
||||
}
|
||||
|
||||
entry.setParent(null);
|
||||
if (entry.mFirstAddedIteration == mIterationCount) {
|
||||
if (!newlyVisibleEntries.remove(entry)) {
|
||||
throw new IllegalStateException("Cannot late-filter entry " + entry.getKey() + " "
|
||||
+ entry + " from " + newlyVisibleEntries + " "
|
||||
+ entry.mFirstAddedIteration);
|
||||
}
|
||||
entry.mFirstAddedIteration = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private void sortList() {
|
||||
// Assign sections to top-level elements and sort their children
|
||||
for (ListEntry entry : mNotifList) {
|
||||
entry.setSection(mSectionsProvider.getSection(entry));
|
||||
if (entry instanceof GroupEntry) {
|
||||
GroupEntry parent = (GroupEntry) entry;
|
||||
for (NotificationEntry child : parent.getChildren()) {
|
||||
child.setSection(0);
|
||||
}
|
||||
parent.sortChildren(sChildComparator);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, sort all top-level elements
|
||||
mNotifList.sort(mTopLevelComparator);
|
||||
}
|
||||
|
||||
private void freeEmptyGroups() {
|
||||
mGroups.values().removeIf(ge -> ge.getSummary() == null && ge.getChildren().isEmpty());
|
||||
}
|
||||
|
||||
private void logParentingChanges() {
|
||||
for (NotificationEntry entry : mAllEntries) {
|
||||
if (entry.getParent() != entry.getPreviousParent()) {
|
||||
Log.i(TAG, String.format(
|
||||
"%s: parent changed from %s to %s",
|
||||
entry.getKey(),
|
||||
entry.getPreviousParent() == null
|
||||
? "null" : entry.getPreviousParent().getKey(),
|
||||
entry.getParent() == null
|
||||
? "null" : entry.getParent().getKey()));
|
||||
}
|
||||
}
|
||||
for (GroupEntry group : mGroups.values()) {
|
||||
if (group.getParent() != group.getPreviousParent()) {
|
||||
Log.i(TAG, String.format(
|
||||
"%s: parent changed from %s to %s",
|
||||
group.getKey(),
|
||||
group.getPreviousParent() == null
|
||||
? "null" : group.getPreviousParent().getKey(),
|
||||
group.getParent() == null
|
||||
? "null" : group.getParent().getKey()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final Comparator<ListEntry> mTopLevelComparator = (o1, o2) -> {
|
||||
|
||||
int cmp = Integer.compare(o1.getSection(), o2.getSection());
|
||||
|
||||
if (cmp == 0) {
|
||||
for (int i = 0; i < mNotifComparators.size(); i++) {
|
||||
cmp = mNotifComparators.get(i).compare(o1, o2);
|
||||
if (cmp != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final NotificationEntry rep1 = o1.getRepresentativeEntry();
|
||||
final NotificationEntry rep2 = o2.getRepresentativeEntry();
|
||||
|
||||
if (cmp == 0) {
|
||||
cmp = rep1.getRanking().getRank() - rep2.getRanking().getRank();
|
||||
}
|
||||
|
||||
if (cmp == 0) {
|
||||
cmp = Long.compare(
|
||||
rep2.getSbn().getNotification().when,
|
||||
rep1.getSbn().getNotification().when);
|
||||
}
|
||||
|
||||
return cmp;
|
||||
};
|
||||
|
||||
private static final Comparator<NotificationEntry> sChildComparator = (o1, o2) -> {
|
||||
int cmp = o1.getRanking().getRank() - o2.getRanking().getRank();
|
||||
|
||||
if (cmp == 0) {
|
||||
cmp = Long.compare(
|
||||
o2.getSbn().getNotification().when,
|
||||
o1.getSbn().getNotification().when);
|
||||
}
|
||||
|
||||
return cmp;
|
||||
};
|
||||
|
||||
private boolean applyFilters(NotificationEntry entry, long now) {
|
||||
NotifFilter filter = findRejectingFilter(entry, now);
|
||||
|
||||
if (filter != entry.mExcludingFilter) {
|
||||
if (entry.mExcludingFilter == null) {
|
||||
Log.i(TAG, String.format(
|
||||
"%s: filtered out by '%s'",
|
||||
entry.getKey(),
|
||||
filter.getName()));
|
||||
} else if (filter == null) {
|
||||
Log.i(TAG, String.format(
|
||||
"%s: no longer filtered out (previous filter was '%s')",
|
||||
entry.getKey(),
|
||||
entry.mExcludingFilter.getName()));
|
||||
} else {
|
||||
Log.i(TAG, String.format(
|
||||
"%s: filter changed: '%s' -> '%s'",
|
||||
entry.getKey(),
|
||||
entry.mExcludingFilter,
|
||||
filter));
|
||||
}
|
||||
|
||||
// Note that groups and summaries can also be filtered out later if they're part of a
|
||||
// malformed group. We currently don't have a great way to track that beyond parenting
|
||||
// change logs. Consider adding something similar to mExcludingFilter for them.
|
||||
entry.mExcludingFilter = filter;
|
||||
}
|
||||
|
||||
return filter != null;
|
||||
}
|
||||
|
||||
@Nullable private NotifFilter findRejectingFilter(NotificationEntry entry, long now) {
|
||||
for (int i = 0; i < mNotifFilters.size(); i++) {
|
||||
NotifFilter filter = mNotifFilters.get(i);
|
||||
if (filter.shouldFilterOut(entry, now)) {
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean applyTopLevelPromoters(NotificationEntry entry) {
|
||||
NotifPromoter promoter = findPromoter(entry);
|
||||
|
||||
if (promoter != entry.mNotifPromoter) {
|
||||
if (entry.mNotifPromoter == null) {
|
||||
Log.i(TAG, String.format(
|
||||
"%s: Entry promoted to top level by '%s'",
|
||||
entry.getKey(),
|
||||
promoter.getName()));
|
||||
} else if (promoter == null) {
|
||||
Log.i(TAG, String.format(
|
||||
"%s: Entry is no longer promoted to top level (previous promoter was '%s')",
|
||||
entry.getKey(),
|
||||
entry.mNotifPromoter.getName()));
|
||||
} else {
|
||||
Log.i(TAG, String.format(
|
||||
"%s: Top-level promoter changed: '%s' -> '%s'",
|
||||
entry.getKey(),
|
||||
entry.mNotifPromoter,
|
||||
promoter));
|
||||
}
|
||||
|
||||
entry.mNotifPromoter = promoter;
|
||||
}
|
||||
|
||||
return promoter != null;
|
||||
}
|
||||
|
||||
@Nullable private NotifPromoter findPromoter(NotificationEntry entry) {
|
||||
for (int i = 0; i < mNotifPromoters.size(); i++) {
|
||||
NotifPromoter promoter = mNotifPromoters.get(i);
|
||||
if (promoter.shouldPromoteToTopLevel(entry)) {
|
||||
return promoter;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void rebuildListIfBefore(@PipelineState.StateName int state) {
|
||||
mPipelineState.requireIsBefore(state);
|
||||
if (mPipelineState.is(STATE_IDLE)) {
|
||||
buildList();
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchOnBeforeTransformGroups(
|
||||
List<ListEntry> entries,
|
||||
List<ListEntry> newlyVisibleEntries) {
|
||||
for (int i = 0; i < mOnBeforeTransformGroupsListeners.size(); i++) {
|
||||
mOnBeforeTransformGroupsListeners.get(i)
|
||||
.onBeforeTransformGroups(entries, newlyVisibleEntries);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchOnBeforeSort(List<ListEntry> entries) {
|
||||
for (int i = 0; i < mOnBeforeSortListeners.size(); i++) {
|
||||
mOnBeforeSortListeners.get(i).onBeforeSort(entries);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchOnBeforeRenderList(List<ListEntry> entries) {
|
||||
for (int i = 0; i < mOnBeforeRenderListListeners.size(); i++) {
|
||||
mOnBeforeRenderListListeners.get(i).onBeforeRenderList(entries);
|
||||
}
|
||||
}
|
||||
|
||||
/** See {@link #setOnRenderListListener(OnRenderListListener)} */
|
||||
public interface OnRenderListListener {
|
||||
/**
|
||||
* Called with the final filtered, grouped, and sorted list.
|
||||
*
|
||||
* @param entries A read-only view into the current notif list. Note that this list is
|
||||
* backed by the live list and will change in response to new pipeline runs.
|
||||
*/
|
||||
void onRenderList(List<ListEntry> entries);
|
||||
}
|
||||
|
||||
private static class DefaultSectionsProvider extends SectionsProvider {
|
||||
DefaultSectionsProvider() {
|
||||
super("DefaultSectionsProvider");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSection(ListEntry entry) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String TAG = "NotifListBuilderImpl";
|
||||
|
||||
private static final int MIN_CHILDREN_FOR_GROUP = 2;
|
||||
}
|
||||
@@ -60,6 +60,8 @@ import com.android.internal.util.ContrastColorUtil;
|
||||
import com.android.systemui.statusbar.InflationTask;
|
||||
import com.android.systemui.statusbar.StatusBarIconView;
|
||||
import com.android.systemui.statusbar.notification.InflationException;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
|
||||
import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
|
||||
import com.android.systemui.statusbar.notification.row.NotificationGuts;
|
||||
@@ -84,7 +86,7 @@ import java.util.Objects;
|
||||
* At the moment, there are many things here that shouldn't be and vice-versa. Hopefully we can
|
||||
* clean this up in the future.
|
||||
*/
|
||||
public final class NotificationEntry {
|
||||
public final class NotificationEntry extends ListEntry {
|
||||
|
||||
private final String mKey;
|
||||
private StatusBarNotification mSbn;
|
||||
@@ -98,6 +100,12 @@ public final class NotificationEntry {
|
||||
/** List of lifetime extenders that are extending the lifetime of this notification. */
|
||||
final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>();
|
||||
|
||||
/** If this notification was filtered out, then the filter that did the filtering. */
|
||||
@Nullable NotifFilter mExcludingFilter;
|
||||
|
||||
/** If this was a group child that was promoted to the top level, then who did the promoting. */
|
||||
@Nullable NotifPromoter mNotifPromoter;
|
||||
|
||||
|
||||
/*
|
||||
* Old members
|
||||
@@ -164,8 +172,8 @@ public final class NotificationEntry {
|
||||
public NotificationEntry(
|
||||
@NonNull StatusBarNotification sbn,
|
||||
@NonNull Ranking ranking) {
|
||||
checkNotNull(sbn);
|
||||
checkNotNull(sbn.getKey());
|
||||
super(checkNotNull(checkNotNull(sbn).getKey()));
|
||||
|
||||
checkNotNull(ranking);
|
||||
|
||||
mKey = sbn.getKey();
|
||||
@@ -173,6 +181,11 @@ public final class NotificationEntry {
|
||||
setRanking(ranking);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NotificationEntry getRepresentativeEntry() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** The key for this notification. Guaranteed to be immutable and unique */
|
||||
public String getKey() {
|
||||
return mKey;
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.notification.collection.init;
|
||||
|
||||
import com.android.systemui.Dumpable;
|
||||
import com.android.systemui.statusbar.notification.collection.GroupEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.ListEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Temporary class that tracks the result of the list builder and dumps it to text when requested.
|
||||
*
|
||||
* Eventually, this will be something that hands off the result of the pipeline to the View layer.
|
||||
*/
|
||||
public class FakePipelineConsumer implements Dumpable {
|
||||
private List<ListEntry> mEntries = Collections.emptyList();
|
||||
|
||||
/** Attach the consumer to the pipeline. */
|
||||
public void attach(NotifListBuilderImpl listBuilder) {
|
||||
listBuilder.setOnRenderListListener(this::onBuildComplete);
|
||||
}
|
||||
|
||||
private void onBuildComplete(List<ListEntry> entries) {
|
||||
mEntries = entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||||
pw.println();
|
||||
pw.println("Active notif tree:");
|
||||
for (int i = 0; i < mEntries.size(); i++) {
|
||||
ListEntry entry = mEntries.get(i);
|
||||
if (entry instanceof GroupEntry) {
|
||||
GroupEntry ge = (GroupEntry) entry;
|
||||
pw.println(dumpGroup(ge, "", i));
|
||||
|
||||
pw.println(dumpEntry(ge.getSummary(), INDENT, -1));
|
||||
for (int j = 0; j < ge.getChildren().size(); j++) {
|
||||
pw.println(dumpEntry(ge.getChildren().get(j), INDENT, j));
|
||||
}
|
||||
} else {
|
||||
pw.println(dumpEntry(entry.getRepresentativeEntry(), "", i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String dumpGroup(GroupEntry entry, String indent, int index) {
|
||||
return String.format(
|
||||
"%s[%d] %s (group)",
|
||||
indent,
|
||||
index,
|
||||
entry.getKey());
|
||||
}
|
||||
|
||||
private String dumpEntry(NotificationEntry entry, String indent, int index) {
|
||||
return String.format(
|
||||
"%s[%s] %s (channel=%s)",
|
||||
indent,
|
||||
index == -1 ? "*" : Integer.toString(index),
|
||||
entry.getKey(),
|
||||
entry.getChannel() != null ? entry.getChannel().getId() : "");
|
||||
}
|
||||
|
||||
private static final String INDENT = " ";
|
||||
}
|
||||
@@ -14,12 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.statusbar.notification;
|
||||
package com.android.systemui.statusbar.notification.collection.init;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.systemui.DumpController;
|
||||
import com.android.systemui.Dumpable;
|
||||
import com.android.systemui.statusbar.NotificationListener;
|
||||
import com.android.systemui.statusbar.notification.collection.NotifCollection;
|
||||
import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -28,21 +34,38 @@ import javax.inject.Singleton;
|
||||
* Initialization code for the new notification pipeline.
|
||||
*/
|
||||
@Singleton
|
||||
public class NewNotifPipeline {
|
||||
public class NewNotifPipeline implements Dumpable {
|
||||
private final NotifCollection mNotifCollection;
|
||||
private final NotifListBuilderImpl mNotifPipeline;
|
||||
private final DumpController mDumpController;
|
||||
|
||||
private final FakePipelineConsumer mFakePipelineConsumer = new FakePipelineConsumer();
|
||||
|
||||
@Inject
|
||||
public NewNotifPipeline(
|
||||
NotifCollection notifCollection) {
|
||||
NotifCollection notifCollection,
|
||||
NotifListBuilderImpl notifPipeline,
|
||||
DumpController dumpController) {
|
||||
mNotifCollection = notifCollection;
|
||||
mNotifPipeline = notifPipeline;
|
||||
mDumpController = dumpController;
|
||||
}
|
||||
|
||||
/** Hooks the new pipeline up to NotificationManager */
|
||||
public void initialize(
|
||||
NotificationListener notificationService) {
|
||||
mFakePipelineConsumer.attach(mNotifPipeline);
|
||||
mNotifPipeline.attach(mNotifCollection);
|
||||
mNotifCollection.attach(notificationService);
|
||||
|
||||
Log.d(TAG, "Notif pipeline initialized");
|
||||
|
||||
mDumpController.registerDumpable("NotifPipeline", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||||
mFakePipelineConsumer.dump(fd, pw, args);
|
||||
}
|
||||
|
||||
private static final String TAG = "NewNotifPipeline";
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.notification.collection.listbuilder;
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.ListEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.NotifCollection;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The system that constructs the current "notification list", the list of notifications that are
|
||||
* currently being displayed to the user.
|
||||
*
|
||||
* The pipeline proceeds through a series of stages in order to produce the final list (see below).
|
||||
* Each stage exposes hooks and listeners for other code to participate.
|
||||
*
|
||||
* This list differs from the canonical one we receive from system server in a few ways:
|
||||
* - Filtered: Some notifications are filtered out. For example, we filter out notifications whose
|
||||
* views haven't been inflated yet. We also filter out some notifications if we're on the lock
|
||||
* screen. To participate, see {@link #addFilter(NotifFilter)}.
|
||||
* - Grouped: Notifications that are part of the same group are clustered together into a single
|
||||
* GroupEntry. These groups are then transformed in order to remove children or completely split
|
||||
* them apart. To participate, see {@link #addPromoter(NotifPromoter)}.
|
||||
* - Sorted: All top-level notifications are sorted. To participate, see
|
||||
* {@link #setSectionsProvider(SectionsProvider)} and {@link #setComparators(List)}
|
||||
*
|
||||
* The exact order of all hooks is as follows:
|
||||
* 0. Collection listeners are fired (see {@link NotifCollection}).
|
||||
* 1. NotifFilters are called on each notification currently in NotifCollection.
|
||||
* 2. Initial grouping is performed (NotificationEntries will have their parents set
|
||||
* appropriately).
|
||||
* 3. OnBeforeTransformGroupListeners are fired
|
||||
* 4. NotifPromoters are called on each notification with a parent
|
||||
* 5. OnBeforeSortListeners are fired
|
||||
* 6. SectionsProvider is called on each top-level entry in the list
|
||||
* 7. The top-level entries are sorted using the provided NotifComparators (plus some additional
|
||||
* built-in logic).
|
||||
* 8. OnBeforeRenderListListeners are fired
|
||||
* 9. The list is handed off to the view layer to be rendered.
|
||||
*/
|
||||
public interface NotifListBuilder {
|
||||
|
||||
/**
|
||||
* Registers a filter with the pipeline. Filters are called on each notification in the order
|
||||
* that they were registered. If any filter returns true, the notification is removed from the
|
||||
* pipeline (and no other filters are called on that notif).
|
||||
*/
|
||||
void addFilter(NotifFilter filter);
|
||||
|
||||
/**
|
||||
* Registers a promoter with the pipeline. Promoters are able to promote child notifications to
|
||||
* top-level, i.e. move a notification that would be a child of a group and make it appear
|
||||
* ungrouped. Promoters are called on each child notification in the order that they are
|
||||
* registered. If any promoter returns true, the notification is removed from the group (and no
|
||||
* other promoters are called on it).
|
||||
*/
|
||||
void addPromoter(NotifPromoter promoter);
|
||||
|
||||
/**
|
||||
* Assigns sections to each top-level entry, where a section is simply an integer. Sections are
|
||||
* the primary metric by which top-level entries are sorted; NotifComparators are only consulted
|
||||
* when two entries are in the same section. The pipeline doesn't assign any particular meaning
|
||||
* to section IDs -- from it's perspective they're just numbers and it sorts them by a simple
|
||||
* numerical comparison.
|
||||
*/
|
||||
void setSectionsProvider(SectionsProvider provider);
|
||||
|
||||
/**
|
||||
* Comparators that are used to sort top-level entries that share the same section. The
|
||||
* comparators are executed in order until one of them returns a non-zero result. If all return
|
||||
* zero, the pipeline falls back to sorting by rank (and, failing that, Notification.when).
|
||||
*/
|
||||
void setComparators(List<NotifComparator> comparators);
|
||||
|
||||
/**
|
||||
* Called after notifications have been filtered and after the initial grouping has been
|
||||
* performed but before NotifPromoters have had a chance to promote children out of groups.
|
||||
*/
|
||||
void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener);
|
||||
|
||||
/**
|
||||
* Called after notifs have been filtered and groups have been determined but before sections
|
||||
* have been determined or the notifs have been sorted.
|
||||
*/
|
||||
void addOnBeforeSortListener(OnBeforeSortListener listener);
|
||||
|
||||
/**
|
||||
* Called at the end of the pipeline after the notif list has been finalized but before it has
|
||||
* been handed off to the view layer.
|
||||
*/
|
||||
void addOnBeforeRenderListListener(OnBeforeRenderListListener listener);
|
||||
|
||||
/**
|
||||
* Returns a read-only view in to the current notification list. If this method is called
|
||||
* during pipeline execution it will return the current state of the list, which will likely
|
||||
* be only partially-generated.
|
||||
*/
|
||||
List<ListEntry> getActiveNotifs();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.notification.collection.listbuilder;
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.ListEntry;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** See {@link NotifListBuilder#addOnBeforeRenderListListener(OnBeforeRenderListListener)} */
|
||||
public interface OnBeforeRenderListListener {
|
||||
/**
|
||||
* Called at the end of the pipeline after the notif list has been finalized but before it has
|
||||
* been handed off to the view layer.
|
||||
*
|
||||
* @param entries The current list of top-level entries. Note that this is a live view into the
|
||||
* current list and will change whenever the pipeline is rerun.
|
||||
*/
|
||||
void onBeforeRenderList(List<ListEntry> entries);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.notification.collection.listbuilder;
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.ListEntry;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** See {@link NotifListBuilder#addOnBeforeSortListener(OnBeforeSortListener)} */
|
||||
public interface OnBeforeSortListener {
|
||||
/**
|
||||
* Called after the notif list has been filtered and grouped but before sections have been
|
||||
* determined or sorting has taken place.
|
||||
*
|
||||
* @param entries The current list of top-level entries. Note that this is a live view into the
|
||||
* current list and will change whenever the pipeline is rerun.
|
||||
*/
|
||||
void onBeforeSort(List<ListEntry> entries);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.notification.collection.listbuilder;
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.ListEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* See
|
||||
* {@link NotifListBuilder#addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener)}
|
||||
*/
|
||||
public interface OnBeforeTransformGroupsListener {
|
||||
/**
|
||||
* Called after notifs have been filtered and grouped but before {@link NotifPromoter}s have
|
||||
* been called.
|
||||
*
|
||||
* @param list The current filtered and grouped list of (top-level) entries. Note that this is
|
||||
* a live view into the current notif list and will change as the list moves through
|
||||
* the pipeline.
|
||||
* @param newlyVisibleEntries The list of all entries (both top-level and children) who have
|
||||
* been added to the list for the first time.
|
||||
*/
|
||||
void onBeforeTransformGroups(List<ListEntry> list, List<ListEntry> newlyVisibleEntries);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.notification.collection.listbuilder;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Used by {@link NotifListBuilderImpl} to track its internal state machine.
|
||||
*/
|
||||
public class PipelineState {
|
||||
|
||||
private @StateName int mState = STATE_IDLE;
|
||||
|
||||
/** Returns true if the current state matches <code>state</code> */
|
||||
public boolean is(@StateName int state) {
|
||||
return state == mState;
|
||||
}
|
||||
|
||||
public @StateName int getState() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
public void setState(@StateName int state) {
|
||||
mState = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the state from <code>(to - 1)</code> to <code>to</code>. If the current state
|
||||
* isn't <code>(to - 1)</code>, throws an exception.
|
||||
*/
|
||||
public void incrementTo(@StateName int to) {
|
||||
if (mState != to - 1) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot increment from state " + mState + " to state " + to);
|
||||
}
|
||||
mState = to;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception if the current state is not <code>state</code>.
|
||||
*/
|
||||
public void requireState(@StateName int state) {
|
||||
if (state != mState) {
|
||||
throw new IllegalStateException(
|
||||
"Required state is <" + state + " but actual state is " + mState);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception if the current state is >= <code>state</code>.
|
||||
*/
|
||||
public void requireIsBefore(@StateName int state) {
|
||||
if (mState >= state) {
|
||||
throw new IllegalStateException(
|
||||
"Required state is <" + state + " but actual state is " + mState);
|
||||
}
|
||||
}
|
||||
|
||||
public static final int STATE_IDLE = 0;
|
||||
public static final int STATE_BUILD_PENDING = 1;
|
||||
public static final int STATE_BUILD_STARTED = 2;
|
||||
public static final int STATE_FILTERING = 3;
|
||||
public static final int STATE_TRANSFORMING = 4;
|
||||
public static final int STATE_SORTING = 5;
|
||||
public static final int STATE_FINALIZING = 6;
|
||||
|
||||
@IntDef(prefix = { "STATE_" }, value = {
|
||||
STATE_IDLE,
|
||||
STATE_BUILD_PENDING,
|
||||
STATE_BUILD_STARTED,
|
||||
STATE_FILTERING,
|
||||
STATE_TRANSFORMING,
|
||||
STATE_SORTING,
|
||||
STATE_FINALIZING,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface StateName {}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.notification.collection.listbuilder.pluggable;
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.ListEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Pluggable for participating in notif sorting. See {@link NotifListBuilder#setComparators(List)}.
|
||||
*/
|
||||
public abstract class NotifComparator
|
||||
extends Pluggable<NotifComparator>
|
||||
implements Comparator<ListEntry> {
|
||||
|
||||
protected NotifComparator(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two ListEntries. Note that these might be either NotificationEntries or GroupEntries.
|
||||
*
|
||||
* @return a negative integer, zero, or a positive integer as the first argument is less than
|
||||
* equal to, or greater than the second (same as standard Comparator<> interface).
|
||||
*/
|
||||
public abstract int compare(ListEntry o1, ListEntry o2);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.notification.collection.listbuilder.pluggable;
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
|
||||
|
||||
/**
|
||||
* Pluggable for participating in notif filtering. See
|
||||
* {@link NotifListBuilder#addFilter(NotifFilter)}.
|
||||
*/
|
||||
public abstract class NotifFilter extends Pluggable<NotifFilter> {
|
||||
protected NotifFilter(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* If returns true, this notification will not be included in the final list displayed to the
|
||||
* user. Filtering is performed on each active notification every time the pipeline is run.
|
||||
* This doesn't necessarily mean that your filter will get called on every notification,
|
||||
* however. If another filter returns true before yours, we'll skip straight to the next notif.
|
||||
*
|
||||
* @param entry The entry in question
|
||||
* @param now A timestamp in SystemClock.uptimeMillis that represents "now" for the purposes of
|
||||
* pipeline execution. This value will be the same for all pluggable calls made
|
||||
* during this pipeline run, giving pluggables a stable concept of "now" to compare
|
||||
* various entries against.
|
||||
* @return True if the notif should be removed from the list
|
||||
*/
|
||||
public abstract boolean shouldFilterOut(NotificationEntry entry, long now);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.notification.collection.listbuilder.pluggable;
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
|
||||
|
||||
/**
|
||||
* Pluggable for participating in notif promotion. Notif promoters can upgrade notifications
|
||||
* from being children of a group to top-level notifications. See
|
||||
* {@link NotifListBuilder#addPromoter(NotifPromoter)}.
|
||||
*/
|
||||
public abstract class NotifPromoter extends Pluggable<NotifPromoter> {
|
||||
protected NotifPromoter(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, the child will be removed from its parent and placed at the top level of the notif
|
||||
* list. By the time this method is called, child.getParent() has been set, so you can
|
||||
* examine it (or any other entries in the notif list) for extra information.
|
||||
*
|
||||
* This method is only called on notifs that are currently children of groups. This doesn't
|
||||
* necessarily mean that your promoter will get called on every child notification, however. If
|
||||
* another promoter returns true before yours, we'll skip straight to the next notif.
|
||||
*/
|
||||
public abstract boolean shouldPromoteToTopLevel(NotificationEntry child);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.notification.collection.listbuilder.pluggable;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
|
||||
|
||||
/**
|
||||
* Generic superclass for chunks of code that can plug into the {@link NotifListBuilder}.
|
||||
*
|
||||
* A pluggable is fundamentally three things:
|
||||
* 1. A name (for debugging purposes)
|
||||
* 2. The functionality that the pluggable provides to the pipeline (this is determined by the
|
||||
* subclass).
|
||||
* 3. A way for the pluggable to inform the pipeline that its state has changed and the pipeline
|
||||
* should be rerun (in this case, the invalidate() method).
|
||||
*
|
||||
* @param <This> The type of the subclass. Subclasses should bind their own type here.
|
||||
*/
|
||||
public abstract class Pluggable<This> {
|
||||
private final String mName;
|
||||
@Nullable private PluggableListener<This> mListener;
|
||||
|
||||
Pluggable(String name) {
|
||||
mName = name;
|
||||
}
|
||||
|
||||
public final String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method when something has caused this pluggable's behavior to change. The pipeline
|
||||
* will be re-run.
|
||||
*/
|
||||
public final void invalidateList() {
|
||||
if (mListener != null) {
|
||||
mListener.onPluggableInvalidated((This) this);
|
||||
}
|
||||
}
|
||||
|
||||
/** Set a listener to be notified when a pluggable is invalidated. */
|
||||
public void setInvalidationListener(PluggableListener<This> listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener interface for when pluggables are invalidated.
|
||||
*
|
||||
* @param <T> The type of pluggable that is being listened to.
|
||||
*/
|
||||
public interface PluggableListener<T> {
|
||||
/** Called whenever {@link #invalidateList()} is called on this pluggable. */
|
||||
void onPluggableInvalidated(T pluggable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.notification.collection.listbuilder.pluggable;
|
||||
|
||||
import com.android.systemui.statusbar.notification.collection.ListEntry;
|
||||
|
||||
/**
|
||||
* Interface for sorting notifications into "sections", such as a heads-upping section, people
|
||||
* section, alerting section, silent section, etc.
|
||||
*/
|
||||
public abstract class SectionsProvider extends Pluggable<SectionsProvider> {
|
||||
|
||||
protected SectionsProvider(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the section that this entry belongs to. A section can be any non-negative integer.
|
||||
* When entries are sorted, they are first sorted by section and then by any remainining
|
||||
* comparators.
|
||||
*/
|
||||
public abstract int getSection(ListEntry entry);
|
||||
}
|
||||
@@ -198,7 +198,6 @@ import com.android.systemui.statusbar.VibratorHelper;
|
||||
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
|
||||
import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
|
||||
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
|
||||
import com.android.systemui.statusbar.notification.NewNotifPipeline;
|
||||
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
|
||||
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
|
||||
import com.android.systemui.statusbar.notification.NotificationClicker;
|
||||
@@ -210,6 +209,7 @@ import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
|
||||
import com.android.systemui.statusbar.notification.VisualStabilityManager;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
|
||||
import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
|
||||
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
|
||||
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
|
||||
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
|
||||
|
||||
@@ -56,12 +56,12 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController;
|
||||
import com.android.systemui.statusbar.VibratorHelper;
|
||||
import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
|
||||
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
|
||||
import com.android.systemui.statusbar.notification.NewNotifPipeline;
|
||||
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
|
||||
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
|
||||
import com.android.systemui.statusbar.notification.VisualStabilityManager;
|
||||
import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
|
||||
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
|
||||
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
|
||||
import com.android.systemui.statusbar.policy.BatteryController;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.util.time;
|
||||
|
||||
/**
|
||||
* Testable wrapper around {@link android.os.SystemClock}.
|
||||
*
|
||||
* Dagger can inject this wrapper into your classes. The implementation just proxies calls to the
|
||||
* real SystemClock.
|
||||
*
|
||||
* In tests, pass an instance of FakeSystemClock, which allows you to control the values returned by
|
||||
* the various getters below.
|
||||
*/
|
||||
public interface SystemClock {
|
||||
/** @see android.os.SystemClock#uptimeMillis() */
|
||||
long uptimeMillis();
|
||||
|
||||
/** @see android.os.SystemClock#elapsedRealtime() */
|
||||
long elapsedRealtime();
|
||||
|
||||
/** @see android.os.SystemClock#elapsedRealtimeNanos() */
|
||||
long elapsedRealtimeNanos();
|
||||
|
||||
/** @see android.os.SystemClock#currentThreadTimeMillis() */
|
||||
long currentThreadTimeMillis();
|
||||
|
||||
/** @see android.os.SystemClock#currentThreadTimeMicro() */
|
||||
long currentThreadTimeMicro();
|
||||
|
||||
/** @see android.os.SystemClock#currentTimeMicro() */
|
||||
long currentTimeMicro();
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.util.time;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** Default implementation of {@link SystemClock}. */
|
||||
public class SystemClockImpl implements SystemClock {
|
||||
@Inject
|
||||
public SystemClockImpl() {}
|
||||
|
||||
@Override
|
||||
public long uptimeMillis() {
|
||||
return android.os.SystemClock.uptimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long elapsedRealtime() {
|
||||
return android.os.SystemClock.elapsedRealtime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long elapsedRealtimeNanos() {
|
||||
return android.os.SystemClock.elapsedRealtimeNanos();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long currentThreadTimeMillis() {
|
||||
return android.os.SystemClock.currentThreadTimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long currentThreadTimeMicro() {
|
||||
return android.os.SystemClock.currentThreadTimeMicro();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long currentTimeMicro() {
|
||||
return android.os.SystemClock.currentTimeMicro();
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import android.annotation.Nullable;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager.Importance;
|
||||
import android.content.Context;
|
||||
import android.os.UserHandle;
|
||||
import android.service.notification.SnoozeCriterion;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
@@ -92,6 +93,10 @@ public class NotificationEntryBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Notification.Builder modifyNotification(Context context) {
|
||||
return mSbnBuilder.modifyNotification(context);
|
||||
}
|
||||
|
||||
public NotificationEntryBuilder setUser(UserHandle user) {
|
||||
mSbnBuilder.setUser(user);
|
||||
return this;
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
|
||||
package com.android.systemui.statusbar;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.app.Notification;
|
||||
import android.content.Context;
|
||||
import android.os.UserHandle;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
|
||||
@@ -32,7 +34,8 @@ public class SbnBuilder {
|
||||
private String mTag;
|
||||
private int mUid;
|
||||
private int mInitialPid;
|
||||
private Notification mNotification = new Notification();
|
||||
@Nullable private Notification mNotification;
|
||||
@Nullable private Notification.Builder mNotificationBuilder;
|
||||
private Notification.BubbleMetadata mBubbleMetadata;
|
||||
private UserHandle mUser = UserHandle.of(0);
|
||||
private String mOverrideGroupKey;
|
||||
@@ -55,9 +58,19 @@ public class SbnBuilder {
|
||||
}
|
||||
|
||||
public StatusBarNotification build() {
|
||||
if (mBubbleMetadata != null) {
|
||||
mNotification.setBubbleMetadata(mBubbleMetadata);
|
||||
Notification notification;
|
||||
if (mNotificationBuilder != null) {
|
||||
notification = mNotificationBuilder.build();
|
||||
} else if (mNotification != null) {
|
||||
notification = mNotification;
|
||||
} else {
|
||||
notification = new Notification();
|
||||
}
|
||||
|
||||
if (mBubbleMetadata != null) {
|
||||
notification.setBubbleMetadata(mBubbleMetadata);
|
||||
}
|
||||
|
||||
return new StatusBarNotification(
|
||||
mPkg,
|
||||
mOpPkg,
|
||||
@@ -65,7 +78,7 @@ public class SbnBuilder {
|
||||
mTag,
|
||||
mUid,
|
||||
mInitialPid,
|
||||
mNotification,
|
||||
notification,
|
||||
mUser,
|
||||
mOverrideGroupKey,
|
||||
mPostTime);
|
||||
@@ -106,6 +119,17 @@ public class SbnBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Notification.Builder modifyNotification(Context context) {
|
||||
if (mNotification != null) {
|
||||
mNotificationBuilder = new Notification.Builder(context, mNotification);
|
||||
mNotification = null;
|
||||
} else if (mNotificationBuilder == null) {
|
||||
mNotificationBuilder = new Notification.Builder(context);
|
||||
}
|
||||
|
||||
return mNotificationBuilder;
|
||||
}
|
||||
|
||||
public SbnBuilder setUser(UserHandle user) {
|
||||
mUser = user;
|
||||
return this;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -111,7 +111,6 @@ import com.android.systemui.statusbar.SuperStatusBarViewFactory;
|
||||
import com.android.systemui.statusbar.VibratorHelper;
|
||||
import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
|
||||
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
|
||||
import com.android.systemui.statusbar.notification.NewNotifPipeline;
|
||||
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryListener;
|
||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||
@@ -120,6 +119,7 @@ import com.android.systemui.statusbar.notification.NotificationInterruptionState
|
||||
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
|
||||
import com.android.systemui.statusbar.notification.VisualStabilityManager;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
|
||||
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
|
||||
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.util.time;
|
||||
|
||||
public class FakeSystemClock implements SystemClock {
|
||||
private boolean mAutoIncrement = true;
|
||||
|
||||
private long mUptimeMillis;
|
||||
private long mElapsedRealtime;
|
||||
private long mElapsedRealtimeNanos;
|
||||
private long mCurrentThreadTimeMillis;
|
||||
private long mCurrentThreadTimeMicro;
|
||||
private long mCurrentTimeMicro;
|
||||
|
||||
@Override
|
||||
public long uptimeMillis() {
|
||||
long value = mUptimeMillis;
|
||||
if (mAutoIncrement) {
|
||||
mUptimeMillis++;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long elapsedRealtime() {
|
||||
long value = mElapsedRealtime;
|
||||
if (mAutoIncrement) {
|
||||
mElapsedRealtime++;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long elapsedRealtimeNanos() {
|
||||
long value = mElapsedRealtimeNanos;
|
||||
if (mAutoIncrement) {
|
||||
mElapsedRealtimeNanos++;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long currentThreadTimeMillis() {
|
||||
long value = mCurrentThreadTimeMillis;
|
||||
if (mAutoIncrement) {
|
||||
mCurrentThreadTimeMillis++;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long currentThreadTimeMicro() {
|
||||
long value = mCurrentThreadTimeMicro;
|
||||
if (mAutoIncrement) {
|
||||
mCurrentThreadTimeMicro++;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long currentTimeMicro() {
|
||||
long value = mCurrentTimeMicro;
|
||||
if (mAutoIncrement) {
|
||||
mCurrentTimeMicro++;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setUptimeMillis(long uptimeMillis) {
|
||||
mUptimeMillis = uptimeMillis;
|
||||
}
|
||||
|
||||
public void setElapsedRealtime(long elapsedRealtime) {
|
||||
mElapsedRealtime = elapsedRealtime;
|
||||
}
|
||||
|
||||
public void setElapsedRealtimeNanos(long elapsedRealtimeNanos) {
|
||||
mElapsedRealtimeNanos = elapsedRealtimeNanos;
|
||||
}
|
||||
|
||||
public void setCurrentThreadTimeMillis(long currentThreadTimeMillis) {
|
||||
mCurrentThreadTimeMillis = currentThreadTimeMillis;
|
||||
}
|
||||
|
||||
public void setCurrentThreadTimeMicro(long currentThreadTimeMicro) {
|
||||
mCurrentThreadTimeMicro = currentThreadTimeMicro;
|
||||
}
|
||||
|
||||
public void setCurrentTimeMicro(long currentTimeMicro) {
|
||||
mCurrentTimeMicro = currentTimeMicro;
|
||||
}
|
||||
|
||||
/** If true, each call to get____ will be one higher than the previous call to that method. */
|
||||
public void setAutoIncrement(boolean autoIncrement) {
|
||||
mAutoIncrement = autoIncrement;
|
||||
}
|
||||
}
|
||||
@@ -18,13 +18,13 @@ package com.android.statusbartest;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.view.View;
|
||||
import android.content.Intent;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.StatusBarManager;
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user