• KeyguardSliceView.java


      1 /*
      2  * Copyright (C) 2017 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.keyguard;
     18 
     19 import android.animation.LayoutTransition;
     20 import android.animation.ObjectAnimator;
     21 import android.animation.PropertyValuesHolder;
     22 import android.annotation.ColorInt;
     23 import android.app.PendingIntent;
     24 import android.arch.lifecycle.LiveData;
     25 import android.arch.lifecycle.Observer;
     26 import android.content.Context;
     27 import android.graphics.Color;
     28 import android.graphics.drawable.Drawable;
     29 import android.net.Uri;
     30 import android.provider.Settings;
     31 import android.text.Layout;
     32 import android.text.TextUtils;
     33 import android.text.TextUtils.TruncateAt;
     34 import android.util.AttributeSet;
     35 import android.util.Log;
     36 import android.view.View;
     37 import android.view.ViewGroup;
     38 import android.view.animation.Animation;
     39 import android.widget.Button;
     40 import android.widget.LinearLayout;
     41 import android.widget.TextView;
     42 
     43 import com.android.internal.annotations.VisibleForTesting;
     44 import com.android.internal.graphics.ColorUtils;
     45 import com.android.settingslib.Utils;
     46 import com.android.systemui.Dependency;
     47 import com.android.systemui.Interpolators;
     48 import com.android.systemui.R;
     49 import com.android.systemui.keyguard.KeyguardSliceProvider;
     50 import com.android.systemui.statusbar.AlphaOptimizedTextView;
     51 import com.android.systemui.statusbar.policy.ConfigurationController;
     52 import com.android.systemui.tuner.TunerService;
     53 import com.android.systemui.util.wakelock.KeepAwakeAnimationListener;
     54 
     55 import java.util.ArrayList;
     56 import java.util.HashMap;
     57 import java.util.List;
     58 import java.util.function.Consumer;
     59 
     60 import androidx.slice.Slice;
     61 import androidx.slice.SliceItem;
     62 import androidx.slice.SliceViewManager;
     63 import androidx.slice.core.SliceQuery;
     64 import androidx.slice.widget.ListContent;
     65 import androidx.slice.widget.RowContent;
     66 import androidx.slice.widget.SliceLiveData;
     67 
     68 /**
     69  * View visible under the clock on the lock screen and AoD.
     70  */
     71 public class KeyguardSliceView extends LinearLayout implements View.OnClickListener,
     72         Observer<Slice>, TunerService.Tunable, ConfigurationController.ConfigurationListener {
     73 
     74     private static final String TAG = "KeyguardSliceView";
     75     public static final int DEFAULT_ANIM_DURATION = 550;
     76 
     77     private final HashMap<View, PendingIntent> mClickActions;
     78     private Uri mKeyguardSliceUri;
     79     @VisibleForTesting
     80     TextView mTitle;
     81     private Row mRow;
     82     private int mTextColor;
     83     private float mDarkAmount = 0;
     84 
     85     private LiveData<Slice> mLiveData;
     86     private int mIconSize;
     87     /**
     88      * Runnable called whenever the view contents change.
     89      */
     90     private Runnable mContentChangeListener;
     91     private boolean mHasHeader;
     92     private Slice mSlice;
     93     private boolean mPulsing;
     94 
     95     public KeyguardSliceView(Context context) {
     96         this(context, null, 0);
     97     }
     98 
     99     public KeyguardSliceView(Context context, AttributeSet attrs) {
    100         this(context, attrs, 0);
    101     }
    102 
    103     public KeyguardSliceView(Context context, AttributeSet attrs, int defStyle) {
    104         super(context, attrs, defStyle);
    105 
    106         TunerService tunerService = Dependency.get(TunerService.class);
    107         tunerService.addTunable(this, Settings.Secure.KEYGUARD_SLICE_URI);
    108 
    109         mClickActions = new HashMap<>();
    110 
    111         LayoutTransition transition = new LayoutTransition();
    112         transition.setStagger(LayoutTransition.CHANGE_APPEARING, DEFAULT_ANIM_DURATION / 2);
    113         transition.setDuration(LayoutTransition.APPEARING, DEFAULT_ANIM_DURATION);
    114         transition.setDuration(LayoutTransition.DISAPPEARING, DEFAULT_ANIM_DURATION / 2);
    115         transition.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
    116         transition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
    117         transition.setInterpolator(LayoutTransition.APPEARING, Interpolators.FAST_OUT_SLOW_IN);
    118         transition.setInterpolator(LayoutTransition.DISAPPEARING, Interpolators.ALPHA_OUT);
    119         transition.setAnimateParentHierarchy(false);
    120         transition.addTransitionListener(new SliceViewTransitionListener());
    121         setLayoutTransition(transition);
    122     }
    123 
    124     @Override
    125     protected void onFinishInflate() {
    126         super.onFinishInflate();
    127         mTitle = findViewById(R.id.title);
    128         mRow = findViewById(R.id.row);
    129         mTextColor = Utils.getColorAttr(mContext, R.attr.wallpaperTextColor);
    130     }
    131 
    132     @Override
    133     protected void onAttachedToWindow() {
    134         super.onAttachedToWindow();
    135 
    136         // Make sure we always have the most current slice
    137         mLiveData.observeForever(this);
    138         Dependency.get(ConfigurationController.class).addCallback(this);
    139     }
    140 
    141     @Override
    142     protected void onDetachedFromWindow() {
    143         super.onDetachedFromWindow();
    144 
    145         mLiveData.removeObserver(this);
    146         Dependency.get(ConfigurationController.class).removeCallback(this);
    147     }
    148 
    149     private void showSlice() {
    150         if (mPulsing || mSlice == null) {
    151             mTitle.setVisibility(GONE);
    152             mRow.setVisibility(GONE);
    153             if (mContentChangeListener != null) {
    154                 mContentChangeListener.run();
    155             }
    156             return;
    157         }
    158 
    159         ListContent lc = new ListContent(getContext(), mSlice);
    160         mHasHeader = lc.hasHeader();
    161         List<SliceItem> subItems = new ArrayList<SliceItem>();
    162         for (int i = 0; i < lc.getRowItems().size(); i++) {
    163             SliceItem subItem = lc.getRowItems().get(i);
    164             String itemUri = subItem.getSlice().getUri().toString();
    165             // Filter out the action row
    166             if (!KeyguardSliceProvider.KEYGUARD_ACTION_URI.equals(itemUri)) {
    167                 subItems.add(subItem);
    168             }
    169         }
    170         if (!mHasHeader) {
    171             mTitle.setVisibility(GONE);
    172         } else {
    173             mTitle.setVisibility(VISIBLE);
    174 
    175             // If there's a header it'll be the first subitem
    176             RowContent header = new RowContent(getContext(), subItems.get(0),
    177                     true /* showStartItem */);
    178             SliceItem mainTitle = header.getTitleItem();
    179             CharSequence title = mainTitle != null ? mainTitle.getText() : null;
    180             mTitle.setText(title);
    181         }
    182 
    183         mClickActions.clear();
    184         final int subItemsCount = subItems.size();
    185         final int blendedColor = getTextColor();
    186         final int startIndex = mHasHeader ? 1 : 0; // First item is header; skip it
    187         mRow.setVisibility(subItemsCount > 0 ? VISIBLE : GONE);
    188         for (int i = startIndex; i < subItemsCount; i++) {
    189             SliceItem item = subItems.get(i);
    190             RowContent rc = new RowContent(getContext(), item, true /* showStartItem */);
    191             final Uri itemTag = item.getSlice().getUri();
    192             // Try to reuse the view if already exists in the layout
    193             KeyguardSliceButton button = mRow.findViewWithTag(itemTag);
    194             if (button == null) {
    195                 button = new KeyguardSliceButton(mContext);
    196                 button.setTextColor(blendedColor);
    197                 button.setTag(itemTag);
    198                 final int viewIndex = i - (mHasHeader ? 1 : 0);
    199                 mRow.addView(button, viewIndex);
    200             }
    201 
    202             PendingIntent pendingIntent = null;
    203             if (rc.getPrimaryAction() != null) {
    204                 pendingIntent = rc.getPrimaryAction().getAction();
    205             }
    206             mClickActions.put(button, pendingIntent);
    207 
    208             final SliceItem titleItem = rc.getTitleItem();
    209             button.setText(titleItem == null ? null : titleItem.getText());
    210             button.setContentDescription(rc.getContentDescription());
    211 
    212             Drawable iconDrawable = null;
    213             SliceItem icon = SliceQuery.find(item.getSlice(),
    214                     android.app.slice.SliceItem.FORMAT_IMAGE);
    215             if (icon != null) {
    216                 iconDrawable = icon.getIcon().loadDrawable(mContext);
    217                 final int width = (int) (iconDrawable.getIntrinsicWidth()
    218                         / (float) iconDrawable.getIntrinsicHeight() * mIconSize);
    219                 iconDrawable.setBounds(0, 0, Math.max(width, 1), mIconSize);
    220             }
    221             button.setCompoundDrawables(iconDrawable, null, null, null);
    222             button.setOnClickListener(this);
    223             button.setClickable(pendingIntent != null);
    224         }
    225 
    226         // Removing old views
    227         for (int i = 0; i < mRow.getChildCount(); i++) {
    228             View child = mRow.getChildAt(i);
    229             if (!mClickActions.containsKey(child)) {
    230                 mRow.removeView(child);
    231                 i--;
    232             }
    233         }
    234 
    235         if (mContentChangeListener != null) {
    236             mContentChangeListener.run();
    237         }
    238     }
    239 
    240     public void setPulsing(boolean pulsing, boolean animate) {
    241         mPulsing = pulsing;
    242         LayoutTransition transition = getLayoutTransition();
    243         if (!animate) {
    244             setLayoutTransition(null);
    245         }
    246         showSlice();
    247         if (!animate) {
    248             setLayoutTransition(transition);
    249         }
    250     }
    251 
    252     /**
    253      * Breaks a string in 2 lines where both have similar character count
    254      * but first line is always longer.
    255      *
    256      * @param charSequence Original text.
    257      * @return Optimal string.
    258      */
    259     private static CharSequence findBestLineBreak(CharSequence charSequence) {
    260         if (TextUtils.isEmpty(charSequence)) {
    261             return charSequence;
    262         }
    263 
    264         String source = charSequence.toString();
    265         // Ignore if there is only 1 word,
    266         // or if line breaks were manually set.
    267         if (source.contains("
    ") || !source.contains(" ")) {
    268             return source;
    269         }
    270 
    271         final String[] words = source.split(" ");
    272         final StringBuilder optimalString = new StringBuilder(source.length());
    273         int current = 0;
    274         while (optimalString.length() < source.length() - optimalString.length()) {
    275             optimalString.append(words[current]);
    276             if (current < words.length - 1) {
    277                 optimalString.append(" ");
    278             }
    279             current++;
    280         }
    281         optimalString.append("
    ");
    282         for (int i = current; i < words.length; i++) {
    283             optimalString.append(words[i]);
    284             if (current < words.length - 1) {
    285                 optimalString.append(" ");
    286             }
    287         }
    288 
    289         return optimalString.toString();
    290     }
    291 
    292     public void setDarkAmount(float darkAmount) {
    293         mDarkAmount = darkAmount;
    294         mRow.setDarkAmount(darkAmount);
    295         updateTextColors();
    296     }
    297 
    298     private void updateTextColors() {
    299         final int blendedColor = getTextColor();
    300         mTitle.setTextColor(blendedColor);
    301         int childCount = mRow.getChildCount();
    302         for (int i = 0; i < childCount; i++) {
    303             View v = mRow.getChildAt(i);
    304             if (v instanceof Button) {
    305                 ((Button) v).setTextColor(blendedColor);
    306             }
    307         }
    308     }
    309 
    310     @Override
    311     public void onClick(View v) {
    312         final PendingIntent action = mClickActions.get(v);
    313         if (action != null) {
    314             try {
    315                 action.send();
    316             } catch (PendingIntent.CanceledException e) {
    317                 Log.i(TAG, "Pending intent cancelled, nothing to launch", e);
    318             }
    319         }
    320     }
    321 
    322     /**
    323      * Runnable that gets invoked every time the title or the row visibility changes.
    324      * @param contentChangeListener The listener.
    325      */
    326     public void setContentChangeListener(Runnable contentChangeListener) {
    327         mContentChangeListener = contentChangeListener;
    328     }
    329 
    330     public boolean hasHeader() {
    331         return mHasHeader;
    332     }
    333 
    334     /**
    335      * LiveData observer lifecycle.
    336      * @param slice the new slice content.
    337      */
    338     @Override
    339     public void onChanged(Slice slice) {
    340         mSlice = slice;
    341         showSlice();
    342     }
    343 
    344     @Override
    345     public void onTuningChanged(String key, String newValue) {
    346         setupUri(newValue);
    347     }
    348 
    349     public void setupUri(String uriString) {
    350         if (uriString == null) {
    351             uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI;
    352         }
    353 
    354         boolean wasObserving = false;
    355         if (mLiveData != null && mLiveData.hasActiveObservers()) {
    356             wasObserving = true;
    357             mLiveData.removeObserver(this);
    358         }
    359 
    360         mKeyguardSliceUri = Uri.parse(uriString);
    361         mLiveData = SliceLiveData.fromUri(mContext, mKeyguardSliceUri);
    362 
    363         if (wasObserving) {
    364             mLiveData.observeForever(this);
    365         }
    366     }
    367 
    368     @VisibleForTesting
    369     int getTextColor() {
    370         return ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
    371     }
    372 
    373     @VisibleForTesting
    374     void setTextColor(@ColorInt int textColor) {
    375         mTextColor = textColor;
    376         updateTextColors();
    377     }
    378 
    379     @Override
    380     public void onDensityOrFontScaleChanged() {
    381         mIconSize = mContext.getResources().getDimensionPixelSize(R.dimen.widget_icon_size);
    382     }
    383 
    384     public void refresh() {
    385         Slice slice = SliceViewManager.getInstance(getContext()).bindSlice(mKeyguardSliceUri);
    386         onChanged(slice);
    387     }
    388 
    389     public static class Row extends LinearLayout {
    390 
    391         /**
    392          * This view is visible in AOD, which means that the device will sleep if we
    393          * don't hold a wake lock. We want to enter doze only after all views have reached
    394          * their desired positions.
    395          */
    396         private final Animation.AnimationListener mKeepAwakeListener;
    397         private float mDarkAmount;
    398 
    399         public Row(Context context) {
    400             this(context, null);
    401         }
    402 
    403         public Row(Context context, AttributeSet attrs) {
    404             this(context, attrs, 0);
    405         }
    406 
    407         public Row(Context context, AttributeSet attrs, int defStyleAttr) {
    408             this(context, attrs, defStyleAttr, 0);
    409         }
    410 
    411         public Row(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    412             super(context, attrs, defStyleAttr, defStyleRes);
    413             mKeepAwakeListener = new KeepAwakeAnimationListener(mContext);
    414         }
    415 
    416         @Override
    417         protected void onFinishInflate() {
    418             LayoutTransition transition = new LayoutTransition();
    419             transition.setDuration(DEFAULT_ANIM_DURATION);
    420 
    421             PropertyValuesHolder left = PropertyValuesHolder.ofInt("left", 0, 1);
    422             PropertyValuesHolder right = PropertyValuesHolder.ofInt("right", 0, 1);
    423             ObjectAnimator changeAnimator = ObjectAnimator.ofPropertyValuesHolder((Object) null,
    424                     left, right);
    425             transition.setAnimator(LayoutTransition.CHANGE_APPEARING, changeAnimator);
    426             transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, changeAnimator);
    427             transition.setInterpolator(LayoutTransition.CHANGE_APPEARING,
    428                     Interpolators.ACCELERATE_DECELERATE);
    429             transition.setInterpolator(LayoutTransition.CHANGE_DISAPPEARING,
    430                     Interpolators.ACCELERATE_DECELERATE);
    431             transition.setStartDelay(LayoutTransition.CHANGE_APPEARING, DEFAULT_ANIM_DURATION);
    432             transition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, DEFAULT_ANIM_DURATION);
    433 
    434             ObjectAnimator appearAnimator = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f);
    435             transition.setAnimator(LayoutTransition.APPEARING, appearAnimator);
    436             transition.setInterpolator(LayoutTransition.APPEARING, Interpolators.ALPHA_IN);
    437 
    438             ObjectAnimator disappearAnimator = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f);
    439             transition.setInterpolator(LayoutTransition.DISAPPEARING, Interpolators.ALPHA_OUT);
    440             transition.setDuration(LayoutTransition.DISAPPEARING, DEFAULT_ANIM_DURATION / 4);
    441             transition.setAnimator(LayoutTransition.DISAPPEARING, disappearAnimator);
    442 
    443             transition.setAnimateParentHierarchy(false);
    444             setLayoutTransition(transition);
    445         }
    446 
    447         @Override
    448         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    449             int width = MeasureSpec.getSize(widthMeasureSpec);
    450             int childCount = getChildCount();
    451             for (int i = 0; i < childCount; i++) {
    452                 View child = getChildAt(i);
    453                 if (child instanceof KeyguardSliceButton) {
    454                     ((KeyguardSliceButton) child).setMaxWidth(width / childCount);
    455                 }
    456             }
    457             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    458         }
    459 
    460         public void setDarkAmount(float darkAmount) {
    461             boolean isAwake = darkAmount != 0;
    462             boolean wasAwake = mDarkAmount != 0;
    463             if (isAwake == wasAwake) {
    464                 return;
    465             }
    466             mDarkAmount = darkAmount;
    467             setLayoutAnimationListener(isAwake ? null : mKeepAwakeListener);
    468         }
    469 
    470         @Override
    471         public boolean hasOverlappingRendering() {
    472             return false;
    473         }
    474     }
    475 
    476     /**
    477      * Representation of an item that appears under the clock on main keyguard message.
    478      */
    479     @VisibleForTesting
    480     static class KeyguardSliceButton extends Button implements
    481             ConfigurationController.ConfigurationListener {
    482 
    483         public KeyguardSliceButton(Context context) {
    484             super(context, null /* attrs */, 0 /* styleAttr */,
    485                     com.android.keyguard.R.style.TextAppearance_Keyguard_Secondary);
    486             onDensityOrFontScaleChanged();
    487             setEllipsize(TruncateAt.END);
    488         }
    489 
    490         @Override
    491         protected void onAttachedToWindow() {
    492             super.onAttachedToWindow();
    493             Dependency.get(ConfigurationController.class).addCallback(this);
    494         }
    495 
    496         @Override
    497         protected void onDetachedFromWindow() {
    498             super.onDetachedFromWindow();
    499             Dependency.get(ConfigurationController.class).removeCallback(this);
    500         }
    501 
    502         @Override
    503         public void onDensityOrFontScaleChanged() {
    504             updatePadding();
    505         }
    506 
    507         @Override
    508         public void setText(CharSequence text, BufferType type) {
    509             super.setText(text, type);
    510             updatePadding();
    511         }
    512 
    513         private void updatePadding() {
    514             boolean hasText = !TextUtils.isEmpty(getText());
    515             int horizontalPadding = (int) getContext().getResources()
    516                     .getDimension(R.dimen.widget_horizontal_padding) / 2;
    517             setPadding(horizontalPadding, 0, horizontalPadding * (hasText ? 1 : -1), 0);
    518             setCompoundDrawablePadding((int) mContext.getResources()
    519                     .getDimension(R.dimen.widget_icon_padding));
    520         }
    521 
    522         @Override
    523         public void setTextColor(int color) {
    524             super.setTextColor(color);
    525             updateDrawableColors();
    526         }
    527 
    528         @Override
    529         public void setCompoundDrawables(Drawable left, Drawable top, Drawable right,
    530                 Drawable bottom) {
    531             super.setCompoundDrawables(left, top, right, bottom);
    532             updateDrawableColors();
    533             updatePadding();
    534         }
    535 
    536         private void updateDrawableColors() {
    537             final int color = getCurrentTextColor();
    538             for (Drawable drawable : getCompoundDrawables()) {
    539                 if (drawable != null) {
    540                     drawable.setTint(color);
    541                 }
    542             }
    543         }
    544     }
    545 
    546     /**
    547      * A text view that will split its contents in 2 lines when possible.
    548      */
    549     static class TitleView extends AlphaOptimizedTextView {
    550 
    551         public TitleView(Context context) {
    552             super(context);
    553         }
    554 
    555         public TitleView(Context context, AttributeSet attrs) {
    556             super(context, attrs);
    557         }
    558 
    559         public TitleView(Context context, AttributeSet attrs, int defStyleAttr) {
    560             super(context, attrs, defStyleAttr);
    561         }
    562 
    563         public TitleView(Context context, AttributeSet attrs, int defStyleAttr,
    564                 int defStyleRes) {
    565             super(context, attrs, defStyleAttr, defStyleRes);
    566         }
    567 
    568         @Override
    569         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    570             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    571 
    572             Layout layout = getLayout();
    573             int lineCount = layout.getLineCount();
    574             boolean ellipsizing = layout.getEllipsisCount(lineCount - 1) != 0;
    575             if (lineCount > 0 && !ellipsizing) {
    576                 CharSequence title = getText();
    577                 CharSequence bestLineBreak = findBestLineBreak(title);
    578                 if (!TextUtils.equals(title, bestLineBreak)) {
    579                     setText(bestLineBreak);
    580                     super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    581                 }
    582             }
    583         }
    584     }
    585 
    586     private class SliceViewTransitionListener implements LayoutTransition.TransitionListener {
    587         @Override
    588         public void startTransition(LayoutTransition transition, ViewGroup container, View view,
    589                 int transitionType) {
    590             switch (transitionType) {
    591                 case  LayoutTransition.APPEARING:
    592                     int translation = getResources().getDimensionPixelSize(
    593                             R.dimen.pulsing_notification_appear_translation);
    594                     view.setTranslationY(translation);
    595                     view.animate()
    596                             .translationY(0)
    597                             .setDuration(DEFAULT_ANIM_DURATION)
    598                             .setInterpolator(Interpolators.ALPHA_IN)
    599                             .start();
    600                     break;
    601                 case LayoutTransition.DISAPPEARING:
    602                     if (view == mTitle) {
    603                         // Translate the view to the inverse of its height, so the layout event
    604                         // won't misposition it.
    605                         LayoutParams params = (LayoutParams) mTitle.getLayoutParams();
    606                         int margin = params.topMargin + params.bottomMargin;
    607                         mTitle.setTranslationY(-mTitle.getHeight() - margin);
    608                     }
    609                     break;
    610             }
    611         }
    612 
    613         @Override
    614         public void endTransition(LayoutTransition transition, ViewGroup container, View view,
    615                 int transitionType) {
    616 
    617         }
    618     }
    619 }
  • 相关阅读:
    图象处理算法(一)
    使用自定义类实现工程多语言
    SQL的数据类型
    SQL LEFT JOIN
    TADOCommand
    如何获得活动的数据
    TDateTime
    类定义(一)
    mxOutlookBar组件安装和使用
    TADOQuery
  • 原文地址:https://www.cnblogs.com/chaosDemo/p/9665229.html
Copyright © 2020-2023  润新知