Home | History | Annotate | Download | only in statusbar
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.systemui.statusbar;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.app.INotificationManager;
     22 import android.app.NotificationChannel;
     23 import android.app.NotificationManager;
     24 import android.content.Context;
     25 import android.content.pm.ApplicationInfo;
     26 import android.content.pm.PackageInfo;
     27 import android.content.pm.PackageManager;
     28 import android.content.res.ColorStateList;
     29 import android.content.res.TypedArray;
     30 import android.graphics.Canvas;
     31 import android.graphics.drawable.Drawable;
     32 import android.os.Handler;
     33 import android.os.RemoteException;
     34 import android.os.ServiceManager;
     35 import android.service.notification.NotificationListenerService;
     36 import android.service.notification.StatusBarNotification;
     37 import android.util.AttributeSet;
     38 import android.util.Log;
     39 import android.view.View;
     40 import android.view.ViewAnimationUtils;
     41 import android.view.ViewGroup;
     42 import android.view.accessibility.AccessibilityEvent;
     43 import android.widget.FrameLayout;
     44 import android.widget.ImageView;
     45 import android.widget.LinearLayout;
     46 import android.widget.SeekBar;
     47 import android.widget.Switch;
     48 import android.widget.TextView;
     49 
     50 import com.android.internal.logging.MetricsLogger;
     51 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     52 import com.android.settingslib.Utils;
     53 import com.android.systemui.Interpolators;
     54 import com.android.systemui.R;
     55 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
     56 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
     57 import com.android.systemui.statusbar.stack.StackStateAnimator;
     58 
     59 import java.util.Set;
     60 
     61 /**
     62  * The guts of a notification revealed when performing a long press.
     63  */
     64 public class NotificationGuts extends FrameLayout {
     65     private static final String TAG = "NotificationGuts";
     66     private static final long CLOSE_GUTS_DELAY = 8000;
     67 
     68     private Drawable mBackground;
     69     private int mClipTopAmount;
     70     private int mClipBottomAmount;
     71     private int mActualHeight;
     72     private boolean mExposed;
     73 
     74     private Handler mHandler;
     75     private Runnable mFalsingCheck;
     76     private boolean mNeedsFalsingProtection;
     77     private OnGutsClosedListener mClosedListener;
     78     private OnHeightChangedListener mHeightListener;
     79 
     80     private GutsContent mGutsContent;
     81 
     82     public interface GutsContent {
     83 
     84         public void setGutsParent(NotificationGuts listener);
     85 
     86         /**
     87          * @return the view to be shown in the notification guts.
     88          */
     89         public View getContentView();
     90 
     91         /**
     92          * @return the actual height of the content.
     93          */
     94         public int getActualHeight();
     95 
     96         /**
     97          * Called when the guts view have been told to close, typically after an outside
     98          * interaction.
     99          *
    100          * @param save whether the state should be saved.
    101          * @param force whether the guts view should be forced closed regardless of state.
    102          * @return if closing the view has been handled.
    103          */
    104         public boolean handleCloseControls(boolean save, boolean force);
    105 
    106         /**
    107          * @return whether the notification associated with these guts is set to be removed.
    108          */
    109         public boolean willBeRemoved();
    110 
    111         /**
    112          * @return whether these guts are a leavebehind (e.g. {@link NotificationSnooze}).
    113          */
    114         public default boolean isLeavebehind() {
    115             return false;
    116         }
    117     }
    118 
    119     public interface OnGutsClosedListener {
    120         public void onGutsClosed(NotificationGuts guts);
    121     }
    122 
    123     public interface OnHeightChangedListener {
    124         public void onHeightChanged(NotificationGuts guts);
    125     }
    126 
    127     interface OnSettingsClickListener {
    128         void onClick(View v, int appUid);
    129     }
    130 
    131     public NotificationGuts(Context context, AttributeSet attrs) {
    132         super(context, attrs);
    133         setWillNotDraw(false);
    134         mHandler = new Handler();
    135         mFalsingCheck = new Runnable() {
    136             @Override
    137             public void run() {
    138                 if (mNeedsFalsingProtection && mExposed) {
    139                     closeControls(-1 /* x */, -1 /* y */, false /* save */, false /* force */);
    140                 }
    141             }
    142         };
    143         final TypedArray ta = context.obtainStyledAttributes(attrs,
    144                 com.android.internal.R.styleable.Theme, 0, 0);
    145         ta.recycle();
    146     }
    147 
    148     public NotificationGuts(Context context) {
    149         this(context, null);
    150     }
    151 
    152     public void setGutsContent(GutsContent content) {
    153         mGutsContent = content;
    154         removeAllViews();
    155         addView(mGutsContent.getContentView());
    156     }
    157 
    158     public GutsContent getGutsContent() {
    159         return mGutsContent;
    160     }
    161 
    162     public void resetFalsingCheck() {
    163         mHandler.removeCallbacks(mFalsingCheck);
    164         if (mNeedsFalsingProtection && mExposed) {
    165             mHandler.postDelayed(mFalsingCheck, CLOSE_GUTS_DELAY);
    166         }
    167     }
    168 
    169     @Override
    170     protected void onDraw(Canvas canvas) {
    171         draw(canvas, mBackground);
    172     }
    173 
    174     private void draw(Canvas canvas, Drawable drawable) {
    175         int top = mClipTopAmount;
    176         int bottom = mActualHeight - mClipBottomAmount;
    177         if (drawable != null && top < bottom) {
    178             drawable.setBounds(0, top, getWidth(), bottom);
    179             drawable.draw(canvas);
    180         }
    181     }
    182 
    183     @Override
    184     protected void onFinishInflate() {
    185         super.onFinishInflate();
    186         mBackground = mContext.getDrawable(R.drawable.notification_guts_bg);
    187         if (mBackground != null) {
    188             mBackground.setCallback(this);
    189         }
    190     }
    191 
    192     @Override
    193     protected boolean verifyDrawable(Drawable who) {
    194         return super.verifyDrawable(who) || who == mBackground;
    195     }
    196 
    197     @Override
    198     protected void drawableStateChanged() {
    199         drawableStateChanged(mBackground);
    200     }
    201 
    202     private void drawableStateChanged(Drawable d) {
    203         if (d != null && d.isStateful()) {
    204             d.setState(getDrawableState());
    205         }
    206     }
    207 
    208     @Override
    209     public void drawableHotspotChanged(float x, float y) {
    210         if (mBackground != null) {
    211             mBackground.setHotspot(x, y);
    212         }
    213     }
    214 
    215     public void closeControls(boolean leavebehinds, boolean controls, int x, int y, boolean force) {
    216         if (mGutsContent != null) {
    217             if (mGutsContent.isLeavebehind() && leavebehinds) {
    218                 closeControls(x, y, true /* save */, force);
    219             } else if (!mGutsContent.isLeavebehind() && controls) {
    220                 closeControls(x, y, true /* save */, force);
    221             }
    222         }
    223     }
    224 
    225     public void closeControls(int x, int y, boolean save, boolean force) {
    226         if (getWindowToken() == null) {
    227             if (mClosedListener != null) {
    228                 mClosedListener.onGutsClosed(this);
    229             }
    230             return;
    231         }
    232 
    233         if (mGutsContent == null || !mGutsContent.handleCloseControls(save, force)) {
    234             animateClose(x, y);
    235             setExposed(false, mNeedsFalsingProtection);
    236             if (mClosedListener != null) {
    237                 mClosedListener.onGutsClosed(this);
    238             }
    239         }
    240     }
    241 
    242     private void animateClose(int x, int y) {
    243         if (x == -1 || y == -1) {
    244             x = (getLeft() + getRight()) / 2;
    245             y = (getTop() + getHeight() / 2);
    246         }
    247         final double horz = Math.max(getWidth() - x, x);
    248         final double vert = Math.max(getHeight() - y, y);
    249         final float r = (float) Math.hypot(horz, vert);
    250         final Animator a = ViewAnimationUtils.createCircularReveal(this,
    251                 x, y, r, 0);
    252         a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
    253         a.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
    254         a.addListener(new AnimatorListenerAdapter() {
    255             @Override
    256             public void onAnimationEnd(Animator animation) {
    257                 super.onAnimationEnd(animation);
    258                 setVisibility(View.GONE);
    259             }
    260         });
    261         a.start();
    262     }
    263 
    264     public void setActualHeight(int actualHeight) {
    265         mActualHeight = actualHeight;
    266         invalidate();
    267     }
    268 
    269     public int getActualHeight() {
    270         return mActualHeight;
    271     }
    272 
    273     public int getIntrinsicHeight() {
    274         return mGutsContent != null && mExposed ? mGutsContent.getActualHeight() : getHeight();
    275     }
    276 
    277     public void setClipTopAmount(int clipTopAmount) {
    278         mClipTopAmount = clipTopAmount;
    279         invalidate();
    280     }
    281 
    282     public void setClipBottomAmount(int clipBottomAmount) {
    283         mClipBottomAmount = clipBottomAmount;
    284         invalidate();
    285     }
    286 
    287     @Override
    288     public boolean hasOverlappingRendering() {
    289         // Prevents this view from creating a layer when alpha is animating.
    290         return false;
    291     }
    292 
    293     public void setClosedListener(OnGutsClosedListener listener) {
    294         mClosedListener = listener;
    295     }
    296 
    297     public void setHeightChangedListener(OnHeightChangedListener listener) {
    298         mHeightListener = listener;
    299     }
    300 
    301     protected void onHeightChanged() {
    302         if (mHeightListener != null) {
    303             mHeightListener.onHeightChanged(this);
    304         }
    305     }
    306 
    307     public void setExposed(boolean exposed, boolean needsFalsingProtection) {
    308         final boolean wasExposed = mExposed;
    309         mExposed = exposed;
    310         mNeedsFalsingProtection = needsFalsingProtection;
    311         if (mExposed && mNeedsFalsingProtection) {
    312             resetFalsingCheck();
    313         } else {
    314             mHandler.removeCallbacks(mFalsingCheck);
    315         }
    316         if (wasExposed != mExposed && mGutsContent != null) {
    317             final View contentView = mGutsContent.getContentView();
    318             contentView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
    319             if (mExposed) {
    320                 contentView.requestAccessibilityFocus();
    321             }
    322         }
    323     }
    324 
    325     public boolean willBeRemoved() {
    326         return mGutsContent != null ? mGutsContent.willBeRemoved() : false;
    327     }
    328 
    329     public boolean isExposed() {
    330         return mExposed;
    331     }
    332 }
    333