Change NotificationGuts to contain a view

NotificationGuts is now given a view to display, the notification
management controls have been moved into their own view.

NotificationGuts is provided a view to show via a MenuItem.

This allows configuration via the NotificationMenuRowProvider Plugin.

Test: manual
Change-Id: I68cb23ea2cada30cc6e930fa8c03e0aa4014dfe2
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index d9298ed..d4499df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -101,6 +101,8 @@
 import com.android.systemui.SwipeHelper;
 import com.android.systemui.SystemUI;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.statusbar.NotificationData.Entry;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
@@ -247,6 +249,7 @@
 
     // which notification is currently being longpress-examined by the user
     private NotificationGuts mNotificationGutsExposed;
+    private MenuItem mGutsMenuItem;
 
     private KeyboardShortcuts mKeyboardShortcuts;
 
@@ -702,6 +705,7 @@
         }
     }
 
+    @Override
     public void start() {
         mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
         mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
@@ -967,7 +971,7 @@
             entry.row.reInflateViews();
             if (exposedGuts) {
                 mNotificationGutsExposed = entry.row.getGuts();
-                bindGuts(entry.row);
+                bindGuts(entry.row, mGutsMenuItem);
             }
             inflateViews(entry, mStackScroller);
         }
@@ -1032,6 +1036,7 @@
             @Override
             public boolean onDismiss() {
                 AsyncTask.execute(new Runnable() {
+                    @Override
                     public void run() {
                         TaskStackBuilder.create(mContext)
                                 .addNextIntentWithParentStack(intent)
@@ -1045,11 +1050,10 @@
         }, false /* afterKeyguardGone */);
     }
 
-    private void bindGuts(final ExpandableNotificationRow row) {
+    private void bindGuts(final ExpandableNotificationRow row, MenuItem item) {
         row.inflateGuts();
+        row.setGutsView(item);
         final StatusBarNotification sbn = row.getStatusBarNotification();
-        final NotificationChannel channel = row.getEntry().channel;
-        PackageManager pmUser = getPackageManagerForUser(mContext, sbn.getUser().getIdentifier());
         row.setTag(sbn.getPackageName());
         final NotificationGuts guts = row.getGuts();
         guts.setClosedListener((NotificationGuts g) -> {
@@ -1059,43 +1063,48 @@
             mNotificationGutsExposed = null;
         });
 
-        final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
-                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
-
-        final String pkg = sbn.getPackageName();
-        final NotificationGuts.OnSettingsClickListener onSettingsClick =
-                (View v, int appUid) -> {
-                    MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTE_INFO);
-                    guts.resetFalsingCheck();
-                    startAppNotificationSettingsActivity(pkg, appUid);
-                };
-        final View.OnClickListener onDoneClick =
-                (View v) -> {
-                    // If the user has security enabled, show challenge if the setting is changed.
-                    if (guts.hasImportanceChanged()
-                                && isLockscreenPublicMode(sbn.getUser().getIdentifier())
-                                && (mState == StatusBarState.KEYGUARD
-                                        || mState == StatusBarState.SHADE_LOCKED)) {
-                        OnDismissAction dismissAction = new OnDismissAction() {
-                            @Override
-                            public boolean onDismiss() {
-                                closeControls(row, guts, v);
-                                return true;
-                            }
-                        };
-                        onLockedNotificationImportanceChange(dismissAction);
-                    } else {
-                        closeControls(row, guts, v);
-                    }
-                };
-        guts.bindNotification(pmUser, iNotificationManager, sbn, channel,
-                onSettingsClick, onDoneClick, mNonBlockablePkgs);
+        if (item.gutsContent instanceof NotificationInfo) {
+            final NotificationChannel channel = row.getEntry().channel;
+            PackageManager pmUser = getPackageManagerForUser(mContext,
+                    sbn.getUser().getIdentifier());
+            final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
+                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+            final String pkg = sbn.getPackageName();
+            NotificationInfo info = (NotificationInfo) item.gutsContent;
+            final NotificationInfo.OnSettingsClickListener onSettingsClick = (View v,
+                    int appUid) -> {
+                MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTE_INFO);
+                guts.resetFalsingCheck();
+                startAppNotificationSettingsActivity(pkg, appUid);
+            };
+            final View.OnClickListener onDoneClick = (View v) -> {
+                // If the user has security enabled, show challenge if the setting is changed.
+                if (info.hasImportanceChanged()
+                        && isLockscreenPublicMode(sbn.getUser().getIdentifier())
+                        && (mState == StatusBarState.KEYGUARD
+                                || mState == StatusBarState.SHADE_LOCKED)) {
+                    OnDismissAction dismissAction = new OnDismissAction() {
+                        @Override
+                        public boolean onDismiss() {
+                            saveAndCloseNotificationMenu(info, row, guts, v);
+                            return true;
+                        }
+                    };
+                    onLockedNotificationImportanceChange(dismissAction);
+                } else {
+                    saveAndCloseNotificationMenu(info, row, guts, v);
+                }
+            };
+            info.bindNotification(pmUser, iNotificationManager, sbn, channel, onSettingsClick,
+                    onDoneClick,
+                    mNonBlockablePkgs);
+        }
     }
 
-    private void closeControls(
+    private void saveAndCloseNotificationMenu(NotificationInfo info,
             ExpandableNotificationRow row, NotificationGuts guts, View done) {
         guts.resetFalsingCheck();
-
+        info.saveImportance();
         int[] rowLocation = new int[2];
         int[] doneLocation = new int[2];
         row.getLocationOnScreen(rowLocation);
@@ -1111,7 +1120,8 @@
     protected SwipeHelper.LongPressListener getNotificationLongClicker() {
         return new SwipeHelper.LongPressListener() {
             @Override
-            public boolean onLongPress(View v, final int x, final int y) {
+            public boolean onLongPress(View v, final int x, final int y,
+                    MenuItem item) {
                 if (!(v instanceof ExpandableNotificationRow)) {
                     return false;
                 }
@@ -1121,10 +1131,10 @@
                 }
 
                 final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
-                bindGuts(row);
+                bindGuts(row, item);
+                NotificationGuts guts = row.getGuts();
 
                 // Assume we are a status_bar_notification_row
-                final NotificationGuts guts = row.getGuts();
                 if (guts == null) {
                     // This view has no guts. Examples are the more card or the dismiss all view
                     return false;
@@ -1142,6 +1152,7 @@
                 guts.setVisibility(View.INVISIBLE);
                 // Post to ensure the the guts are properly laid out.
                 guts.post(new Runnable() {
+                    @Override
                     public void run() {
                         if (row.getWindowToken() == null) {
                             Log.e(TAG, "Trying to show notification guts, but not attached to "
@@ -1172,6 +1183,7 @@
                         row.closeRemoteInput();
                         mStackScroller.onHeightChanged(row, true /* needsAnimation */);
                         mNotificationGutsExposed = guts;
+                        mGutsMenuItem = item;
                     }
                 });
                 return true;
@@ -1483,6 +1495,7 @@
     }
 
     protected class H extends Handler {
+        @Override
         public void handleMessage(Message m) {
             switch (m.what) {
              case MSG_SHOW_RECENT_APPS:
@@ -1752,6 +1765,7 @@
                 && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
                 mCurrentUserId);
         dismissKeyguardThenExecute(new OnDismissAction() {
+            @Override
             public boolean onDismiss() {
                 new Thread() {
                     @Override
@@ -1802,6 +1816,7 @@
     private final class NotificationClicker implements View.OnClickListener {
         private final int[] mTmpInt2 = new int[2];
 
+        @Override
         public void onClick(final View v) {
             if (!(v instanceof ExpandableNotificationRow)) {
                 Log.e(TAG, "NotificationClicker called on a view that is not a notification row.");
@@ -1845,6 +1860,7 @@
                     && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
                             mCurrentUserId);
             dismissKeyguardThenExecute(new OnDismissAction() {
+                @Override
                 public boolean onDismiss() {
                     if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
                         // Release the HUN notification to the shade.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 3f88ff9..bd15630 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -50,6 +50,7 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
 import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -666,6 +667,13 @@
         mHeadsUpManager = headsUpManager;
     }
 
+    public void setGutsView(MenuItem item) {
+        if (mGuts != null) {
+            item.gutsContent.setInteractionListener(mGuts);
+            mGuts.setGutsContent(item.gutsContent);
+        }
+    }
+
     public void reInflateViews() {
         initDimens();
         if (mIsSummaryWithChildren) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index 83104e6..f6056dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -40,6 +40,7 @@
 import android.view.ViewAnimationUtils;
 import android.view.ViewGroup;
 import android.widget.CompoundButton;
+import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.RadioButton;
@@ -53,6 +54,8 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsContent;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 import java.util.Set;
@@ -60,7 +63,8 @@
 /**
  * The guts of a notification revealed when performing a long press.
  */
-public class NotificationGuts extends LinearLayout {
+public class NotificationGuts extends FrameLayout
+        implements NotificationMenuRowProvider.GutsInteractionListener {
     private static final String TAG = "NotificationGuts";
     private static final long CLOSE_GUTS_DELAY = 8000;
 
@@ -69,24 +73,14 @@
     private int mClipBottomAmount;
     private int mActualHeight;
     private boolean mExposed;
-    private INotificationManager mINotificationManager;
-    private int mStartingUserImportance;
-    private StatusBarNotification mStatusBarNotification;
-    private NotificationChannel mNotificationChannel;
-
-    private View mImportanceGroup;
-    private View mChannelDisabled;
-    private Switch mChannelEnabledSwitch;
-    private RadioButton mMinImportanceButton;
-    private RadioButton mLowImportanceButton;
-    private RadioButton mDefaultImportanceButton;
-    private RadioButton mHighImportanceButton;
 
     private Handler mHandler;
     private Runnable mFalsingCheck;
     private boolean mNeedsFalsingProtection;
     private OnGutsClosedListener mListener;
 
+    private GutsContent mGutsContent;
+
     public interface OnGutsClosedListener {
         public void onGutsClosed(NotificationGuts guts);
     }
@@ -103,11 +97,22 @@
                 }
             }
         };
-        final TypedArray ta =
-                context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Theme, 0, 0);
+        final TypedArray ta = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.Theme, 0, 0);
         ta.recycle();
     }
 
+    public NotificationGuts(Context context) {
+        this(context, null);
+
+    }
+
+    public void setGutsContent(GutsContent content) {
+        mGutsContent = content;
+        removeAllViews();
+        addView(mGutsContent.getContentView());
+    }
+
     public void resetFalsingCheck() {
         mHandler.removeCallbacks(mFalsingCheck);
         if (mNeedsFalsingProtection && mExposed) {
@@ -165,213 +170,23 @@
         void onClick(View v, int appUid);
     }
 
-    void bindNotification(final PackageManager pm, final INotificationManager iNotificationManager,
-            final StatusBarNotification sbn, final NotificationChannel channel,
-            OnSettingsClickListener onSettingsClick,
-            OnClickListener onDoneClick, final Set<String> nonBlockablePkgs) {
-        mINotificationManager = iNotificationManager;
-        mNotificationChannel = channel;
-        mStatusBarNotification = sbn;
-        mStartingUserImportance = channel.getImportance();
-
-        final String pkg = sbn.getPackageName();
-        int appUid = -1;
-        String appname = pkg;
-        Drawable pkgicon = null;
-        try {
-            final ApplicationInfo info = pm.getApplicationInfo(pkg,
-                    PackageManager.MATCH_UNINSTALLED_PACKAGES
-                            | PackageManager.MATCH_DISABLED_COMPONENTS
-                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                            | PackageManager.MATCH_DIRECT_BOOT_AWARE);
-            if (info != null) {
-                appUid = info.uid;
-                appname = String.valueOf(pm.getApplicationLabel(info));
-                pkgicon = pm.getApplicationIcon(info);
-            }
-        } catch (PackageManager.NameNotFoundException e) {
-            // app is gone, just show package name and generic icon
-            pkgicon = pm.getDefaultActivityIcon();
-        }
-
-        // If this is the placeholder channel, don't use our channel-specific text.
-        String appNameText;
-        CharSequence channelNameText;
-        if (channel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
-            appNameText = appname;
-            channelNameText = mContext.getString(R.string.notification_header_default_channel);
-        } else {
-            appNameText = mContext.getString(R.string.notification_importance_header_app, appname);
-            channelNameText = channel.getName();
-        }
-        ((TextView) findViewById(R.id.pkgname)).setText(appNameText);
-        ((TextView) findViewById(R.id.channel_name)).setText(channelNameText);
-
-        // Settings button.
-        final TextView settingsButton = (TextView) findViewById(R.id.more_settings);
-        if (appUid >= 0 && onSettingsClick != null) {
-            final int appUidF = appUid;
-            settingsButton.setOnClickListener(
-                    (View view) -> { onSettingsClick.onClick(view, appUidF); });
-            settingsButton.setText(R.string.notification_more_settings);
-        } else {
-            settingsButton.setVisibility(View.GONE);
-        }
-
-        // Done button.
-        final TextView doneButton = (TextView) findViewById(R.id.done);
-        doneButton.setText(R.string.notification_done);
-        doneButton.setOnClickListener(onDoneClick);
-
-        boolean nonBlockable = false;
-        try {
-            final PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
-            nonBlockable = Utils.isSystemPackage(getResources(), pm, info);
-        } catch (PackageManager.NameNotFoundException e) {
-            // unlikely.
-        }
-        if (nonBlockablePkgs != null) {
-            nonBlockable |= nonBlockablePkgs.contains(pkg);
-        }
-
-        final View importanceButtons = findViewById(R.id.importance_buttons);
-        bindToggles(importanceButtons, mStartingUserImportance, nonBlockable);
-
-        // Importance Text (hardcoded to 4 importance levels)
-        final ViewGroup importanceTextGroup =
-                (ViewGroup) findViewById(R.id.importance_buttons_text);
-        final int size = importanceTextGroup.getChildCount();
-        for (int i = 0; i < size; i++) {
-            int importanceNameResId = 0;
-            int importanceDescResId = 0;
-            switch (i) {
-                case 0:
-                    importanceNameResId = R.string.high_importance;
-                    importanceDescResId = R.string.notification_importance_high;
-                    break;
-                case 1:
-                    importanceNameResId = R.string.default_importance;
-                    importanceDescResId = R.string.notification_importance_default;
-                    break;
-                case 2:
-                    importanceNameResId = R.string.low_importance;
-                    importanceDescResId = R.string.notification_importance_low;
-                    break;
-                case 3:
-                    importanceNameResId = R.string.min_importance;
-                    importanceDescResId = R.string.notification_importance_min;
-                    break;
-                default:
-                    Log.e(TAG, "Too many importance groups in this layout.");
-                    break;
-            }
-            final ViewGroup importanceChildGroup = (ViewGroup) importanceTextGroup.getChildAt(i);
-            ((TextView) importanceChildGroup.getChildAt(0)).setText(importanceNameResId);
-            ((TextView) importanceChildGroup.getChildAt(1)).setText(importanceDescResId);
-        }
-
-        // Top-level importance group
-        mImportanceGroup = findViewById(R.id.importance);
-        mChannelDisabled = findViewById(R.id.channel_disabled);
-        updateImportanceGroup();
-    }
-
-    public boolean hasImportanceChanged() {
-        return mStartingUserImportance != getSelectedImportance();
-    }
-
-    private void saveImportance() {
-        int selectedImportance = getSelectedImportance();
-        if (selectedImportance == mStartingUserImportance) {
-            return;
-        }
-        MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
-                selectedImportance - mStartingUserImportance);
-        mNotificationChannel.setImportance(selectedImportance);
-        try {
-            mINotificationManager.updateNotificationChannelForPackage(
-                    mStatusBarNotification.getPackageName(), mStatusBarNotification.getUid(),
-                    mNotificationChannel);
-        } catch (RemoteException e) {
-            // :(
-        }
-    }
-
-    private int getSelectedImportance() {
-        if (!mChannelEnabledSwitch.isChecked()) {
-            return NotificationManager.IMPORTANCE_NONE;
-        } else if (mMinImportanceButton.isChecked()) {
-            return NotificationManager.IMPORTANCE_MIN;
-        } else if (mLowImportanceButton.isChecked()) {
-            return NotificationManager.IMPORTANCE_LOW;
-        } else if (mDefaultImportanceButton.isChecked()) {
-            return NotificationManager.IMPORTANCE_DEFAULT;
-        } else if (mHighImportanceButton.isChecked()) {
-            return NotificationManager.IMPORTANCE_HIGH;
-        } else {
-            return NotificationManager.IMPORTANCE_UNSPECIFIED;
-        }
-    }
-
-    private void bindToggles(final View importanceButtons, final int importance,
-            final boolean nonBlockable) {
-        // Enabled Switch
-        mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch);
-        mChannelEnabledSwitch.setChecked(importance != NotificationManager.IMPORTANCE_NONE);
-        mChannelEnabledSwitch.setVisibility(nonBlockable ? View.INVISIBLE : View.VISIBLE);
-
-        // Importance Buttons
-        mMinImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.min_importance);
-        mLowImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.low_importance);
-        mDefaultImportanceButton =
-                (RadioButton) importanceButtons.findViewById(R.id.default_importance);
-        mHighImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.high_importance);
-
-        // Set to current importance setting
-        switch (importance) {
-            case NotificationManager.IMPORTANCE_UNSPECIFIED:
-            case NotificationManager.IMPORTANCE_NONE:
-                break;
-            case NotificationManager.IMPORTANCE_MIN:
-                mMinImportanceButton.setChecked(true);
-                break;
-            case NotificationManager.IMPORTANCE_LOW:
-                mLowImportanceButton.setChecked(true);
-                break;
-            case NotificationManager.IMPORTANCE_DEFAULT:
-                mDefaultImportanceButton.setChecked(true);
-                break;
-            case NotificationManager.IMPORTANCE_HIGH:
-            case NotificationManager.IMPORTANCE_MAX:
-                mHighImportanceButton.setChecked(true);
-                break;
-        }
-
-        // Callback when checked.
-        mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
-            resetFalsingCheck();
-            updateImportanceGroup();
-        });
-        ((RadioGroup) importanceButtons).setOnCheckedChangeListener(
-                (buttonView, isChecked) -> { resetFalsingCheck(); });
-    }
-
-    private void updateImportanceGroup() {
-        final boolean disabled = getSelectedImportance() == NotificationManager.IMPORTANCE_NONE;
-        mImportanceGroup.setVisibility(disabled ? View.GONE : View.VISIBLE);
-        mChannelDisabled.setVisibility(disabled ? View.VISIBLE : View.GONE);
-    }
-
     public void closeControls(int x, int y, boolean saveImportance) {
-        if (saveImportance) {
-            saveImportance();
-        }
         if (getWindowToken() == null) {
             if (mListener != null) {
                 mListener.onGutsClosed(this);
             }
             return;
         }
+        if (mGutsContent == null || !mGutsContent.handleCloseControls()) {
+            animateClose(x, y);
+        }
+        setExposed(false, mNeedsFalsingProtection);
+        if (mListener != null) {
+            mListener.onGutsClosed(this);
+        }
+    }
+
+    private void animateClose(int x, int y) {
         if (x == -1 || y == -1) {
             x = (getLeft() + getRight()) / 2;
             y = (getTop() + getHeight() / 2);
@@ -391,10 +206,6 @@
             }
         });
         a.start();
-        setExposed(false, mNeedsFalsingProtection);
-        if (mListener != null) {
-            mListener.onGutsClosed(this);
-        }
     }
 
     public void setActualHeight(int actualHeight) {
@@ -439,4 +250,14 @@
     public boolean isExposed() {
         return mExposed;
     }
+
+    @Override
+    public void onInteraction(View view) {
+        resetFalsingCheck();
+    }
+
+    @Override
+    public void closeGuts(View view) {
+        closeControls(-1 /* x */, -1 /* y */, true /* notify */);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
new file mode 100644
index 0000000..0989846
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -0,0 +1,319 @@
+package com.android.systemui.statusbar;
+
+/*
+ * 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
+ */
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.INotificationManager;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.SeekBar;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settingslib.Utils;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsContent;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsInteractionListener;
+import com.android.systemui.statusbar.NotificationGuts.OnSettingsClickListener;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
+
+import java.util.Set;
+
+/**
+ * The guts of a notification revealed when performing a long press.
+ */
+public class NotificationInfo extends LinearLayout implements GutsContent {
+    private static final String TAG = "InfoGuts";
+
+    private INotificationManager mINotificationManager;
+    private int mStartingUserImportance;
+    private StatusBarNotification mStatusBarNotification;
+    private NotificationChannel mNotificationChannel;
+
+    private ImageView mAutoButton;
+    private TextView mImportanceSummary;
+    private TextView mImportanceTitle;
+    private boolean mAuto;
+
+    private View mImportanceGroup;
+    private View mChannelDisabled;
+    private Switch mChannelEnabledSwitch;
+    private RadioButton mMinImportanceButton;
+    private RadioButton mLowImportanceButton;
+    private RadioButton mDefaultImportanceButton;
+    private RadioButton mHighImportanceButton;
+
+    private GutsInteractionListener mGutsInteractionListener;
+
+    public NotificationInfo(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    interface OnSettingsClickListener {
+        void onClick(View v, int appUid);
+    }
+
+    void bindNotification(final PackageManager pm, final INotificationManager iNotificationManager,
+            final StatusBarNotification sbn, final NotificationChannel channel,
+            OnSettingsClickListener onSettingsClick,
+            OnClickListener onDoneClick, final Set<String> nonBlockablePkgs) {
+        mINotificationManager = iNotificationManager;
+        mNotificationChannel = channel;
+        mStatusBarNotification = sbn;
+        mStartingUserImportance = channel.getImportance();
+
+        final String pkg = sbn.getPackageName();
+        int appUid = -1;
+        String appname = pkg;
+        Drawable pkgicon = null;
+        try {
+            final ApplicationInfo info = pm.getApplicationInfo(pkg,
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES
+                            | PackageManager.MATCH_DISABLED_COMPONENTS
+                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                            | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+            if (info != null) {
+                appUid = info.uid;
+                appname = String.valueOf(pm.getApplicationLabel(info));
+                pkgicon = pm.getApplicationIcon(info);
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            // app is gone, just show package name and generic icon
+            pkgicon = pm.getDefaultActivityIcon();
+        }
+
+        // If this is the placeholder channel, don't use our channel-specific text.
+        String appNameText;
+        CharSequence channelNameText;
+        if (channel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+            appNameText = appname;
+            channelNameText = mContext.getString(R.string.notification_header_default_channel);
+        } else {
+            appNameText = mContext.getString(R.string.notification_importance_header_app, appname);
+            channelNameText = channel.getName();
+        }
+        ((TextView) findViewById(R.id.pkgname)).setText(appNameText);
+        ((TextView) findViewById(R.id.channel_name)).setText(channelNameText);
+
+        // Settings button.
+        final TextView settingsButton = (TextView) findViewById(R.id.more_settings);
+        if (appUid >= 0 && onSettingsClick != null) {
+            final int appUidF = appUid;
+            settingsButton.setOnClickListener(
+                    (View view) -> {
+                        onSettingsClick.onClick(view, appUidF);
+                    });
+            settingsButton.setText(R.string.notification_more_settings);
+        } else {
+            settingsButton.setVisibility(View.GONE);
+        }
+
+        // Done button.
+        final TextView doneButton = (TextView) findViewById(R.id.done);
+        doneButton.setText(R.string.notification_done);
+        doneButton.setOnClickListener(onDoneClick);
+
+        boolean nonBlockable = false;
+        try {
+            final PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
+            nonBlockable = Utils.isSystemPackage(getResources(), pm, info);
+        } catch (PackageManager.NameNotFoundException e) {
+            // unlikely.
+        }
+        if (nonBlockablePkgs != null) {
+            nonBlockable |= nonBlockablePkgs.contains(pkg);
+        }
+
+        final View importanceButtons = findViewById(R.id.importance_buttons);
+        bindToggles(importanceButtons, mStartingUserImportance, nonBlockable);
+
+        // Importance Text (hardcoded to 4 importance levels)
+        final ViewGroup importanceTextGroup = (ViewGroup) findViewById(
+                R.id.importance_buttons_text);
+        final int size = importanceTextGroup.getChildCount();
+        for (int i = 0; i < size; i++) {
+            int importanceNameResId = 0;
+            int importanceDescResId = 0;
+            switch (i) {
+                case 0:
+                    importanceNameResId = R.string.high_importance;
+                    importanceDescResId = R.string.notification_importance_high;
+                    break;
+                case 1:
+                    importanceNameResId = R.string.default_importance;
+                    importanceDescResId = R.string.notification_importance_default;
+                    break;
+                case 2:
+                    importanceNameResId = R.string.low_importance;
+                    importanceDescResId = R.string.notification_importance_low;
+                    break;
+                case 3:
+                    importanceNameResId = R.string.min_importance;
+                    importanceDescResId = R.string.notification_importance_min;
+                    break;
+                default:
+                    Log.e(TAG, "Too many importance groups in this layout.");
+                    break;
+            }
+            final ViewGroup importanceChildGroup = (ViewGroup) importanceTextGroup.getChildAt(i);
+            ((TextView) importanceChildGroup.getChildAt(0)).setText(importanceNameResId);
+            ((TextView) importanceChildGroup.getChildAt(1)).setText(importanceDescResId);
+        }
+
+        // Top-level importance group
+        mImportanceGroup = findViewById(R.id.importance);
+        mChannelDisabled = findViewById(R.id.channel_disabled);
+        updateImportanceGroup();
+    }
+
+    public boolean hasImportanceChanged() {
+        return mStartingUserImportance != getSelectedImportance();
+    }
+
+    public void saveImportance() {
+        int selectedImportance = getSelectedImportance();
+        if (selectedImportance == mStartingUserImportance) {
+            return;
+        }
+        MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
+                selectedImportance - mStartingUserImportance);
+        mNotificationChannel.setImportance(selectedImportance);
+        try {
+            mINotificationManager.updateNotificationChannelForPackage(
+                    mStatusBarNotification.getPackageName(), mStatusBarNotification.getUid(),
+                    mNotificationChannel);
+        } catch (RemoteException e) {
+            // :(
+        }
+    }
+
+    private int getSelectedImportance() {
+        if (!mChannelEnabledSwitch.isChecked()) {
+            return NotificationManager.IMPORTANCE_NONE;
+        } else if (mMinImportanceButton.isChecked()) {
+            return NotificationManager.IMPORTANCE_MIN;
+        } else if (mLowImportanceButton.isChecked()) {
+            return NotificationManager.IMPORTANCE_LOW;
+        } else if (mDefaultImportanceButton.isChecked()) {
+            return NotificationManager.IMPORTANCE_DEFAULT;
+        } else if (mHighImportanceButton.isChecked()) {
+            return NotificationManager.IMPORTANCE_HIGH;
+        } else {
+            return NotificationManager.IMPORTANCE_UNSPECIFIED;
+        }
+    }
+
+    private void bindToggles(final View importanceButtons, final int importance,
+            final boolean nonBlockable) {
+        // Enabled Switch
+        mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch);
+        mChannelEnabledSwitch.setChecked(importance != NotificationManager.IMPORTANCE_NONE);
+        mChannelEnabledSwitch.setVisibility(nonBlockable ? View.INVISIBLE : View.VISIBLE);
+
+        // Importance Buttons
+        mMinImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.min_importance);
+        mLowImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.low_importance);
+        mDefaultImportanceButton = (RadioButton) importanceButtons
+                .findViewById(R.id.default_importance);
+        mHighImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.high_importance);
+
+        // Set to current importance setting
+        switch (importance) {
+            case NotificationManager.IMPORTANCE_UNSPECIFIED:
+            case NotificationManager.IMPORTANCE_NONE:
+                break;
+            case NotificationManager.IMPORTANCE_MIN:
+                mMinImportanceButton.setChecked(true);
+                break;
+            case NotificationManager.IMPORTANCE_LOW:
+                mLowImportanceButton.setChecked(true);
+                break;
+            case NotificationManager.IMPORTANCE_DEFAULT:
+                mDefaultImportanceButton.setChecked(true);
+                break;
+            case NotificationManager.IMPORTANCE_HIGH:
+            case NotificationManager.IMPORTANCE_MAX:
+                mHighImportanceButton.setChecked(true);
+                break;
+        }
+
+        // Callback when checked.
+        mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
+            mGutsInteractionListener.onInteraction(NotificationInfo.this);
+            updateImportanceGroup();
+        });
+        ((RadioGroup) importanceButtons).setOnCheckedChangeListener(
+                (buttonView, isChecked) -> {
+                    mGutsInteractionListener.onInteraction(NotificationInfo.this);
+                });
+    }
+
+    private void updateImportanceGroup() {
+        final boolean disabled = getSelectedImportance() == NotificationManager.IMPORTANCE_NONE;
+        mImportanceGroup.setVisibility(disabled ? View.GONE : View.VISIBLE);
+        mChannelDisabled.setVisibility(disabled ? View.VISIBLE : View.GONE);
+    }
+
+    public void closeControls() {
+        if (mGutsInteractionListener != null) {
+            mGutsInteractionListener.closeGuts(this);
+        }
+    }
+
+    @Override
+    public void setInteractionListener(GutsInteractionListener listener) {
+        mGutsInteractionListener = listener;
+    }
+
+    @Override
+    public View getContentView() {
+        return this;
+    }
+
+    @Override
+    public boolean handleCloseControls() {
+        return false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
index e728d10..b4ed16a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -102,7 +102,9 @@
     public static MenuItem getSettingsMenuItem(Context context) {
         Drawable d = context.getResources().getDrawable(R.drawable.ic_settings);
         String s = context.getResources().getString(R.string.notification_menu_gear_description);
-        MenuItem settings = new MenuItem(d, s);
+        NotificationInfo content = (NotificationInfo) LayoutInflater.from(context).inflate(
+                R.layout.notification_info, null, false);
+        MenuItem settings = new MenuItem(d, s, content);
         return settings;
     }