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 }
原文地址:https://www.cnblogs.com/chaosDemo/p/9665229.html