Using androidx package and new API, also splitting content into multiple views for better animation support. Bug: 64155983 Test: visual, see data from provider propagate to AoD Change-Id: I74b5511d582e7ec1f6ffe5dbc5595f54b9ccb202
316 lines
10 KiB
Java
316 lines
10 KiB
Java
/*
|
|
* 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
|
|
*/
|
|
|
|
package com.android.keyguard;
|
|
|
|
import android.app.PendingIntent;
|
|
import android.arch.lifecycle.LiveData;
|
|
import android.arch.lifecycle.Observer;
|
|
import android.content.Context;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Color;
|
|
import android.graphics.Paint;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.net.Uri;
|
|
import android.provider.Settings;
|
|
import android.util.AttributeSet;
|
|
import android.util.Log;
|
|
import android.view.View;
|
|
import android.widget.Button;
|
|
import android.widget.LinearLayout;
|
|
import android.widget.TextView;
|
|
|
|
import com.android.internal.graphics.ColorUtils;
|
|
import com.android.settingslib.Utils;
|
|
import com.android.systemui.Dependency;
|
|
import com.android.systemui.R;
|
|
import com.android.systemui.keyguard.KeyguardSliceProvider;
|
|
import com.android.systemui.tuner.TunerService;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.function.Consumer;
|
|
|
|
import androidx.app.slice.Slice;
|
|
import androidx.app.slice.SliceItem;
|
|
import androidx.app.slice.core.SliceQuery;
|
|
import androidx.app.slice.widget.SliceLiveData;
|
|
|
|
/**
|
|
* View visible under the clock on the lock screen and AoD.
|
|
*/
|
|
public class KeyguardSliceView extends LinearLayout implements View.OnClickListener,
|
|
Observer<Slice>, TunerService.Tunable {
|
|
|
|
private static final String TAG = "KeyguardSliceView";
|
|
private final HashMap<View, PendingIntent> mClickActions;
|
|
private Uri mKeyguardSliceUri;
|
|
private TextView mTitle;
|
|
private LinearLayout mRow;
|
|
private int mTextColor;
|
|
private float mDarkAmount = 0;
|
|
|
|
private LiveData<Slice> mLiveData;
|
|
private int mIconSize;
|
|
private Consumer<Boolean> mListener;
|
|
private boolean mHasHeader;
|
|
|
|
public KeyguardSliceView(Context context) {
|
|
this(context, null, 0);
|
|
}
|
|
|
|
public KeyguardSliceView(Context context, AttributeSet attrs) {
|
|
this(context, attrs, 0);
|
|
}
|
|
|
|
public KeyguardSliceView(Context context, AttributeSet attrs, int defStyle) {
|
|
super(context, attrs, defStyle);
|
|
|
|
TunerService tunerService = Dependency.get(TunerService.class);
|
|
tunerService.addTunable(this, Settings.Secure.KEYGUARD_SLICE_URI);
|
|
|
|
mClickActions = new HashMap<>();
|
|
}
|
|
|
|
@Override
|
|
protected void onFinishInflate() {
|
|
super.onFinishInflate();
|
|
mTitle = findViewById(R.id.title);
|
|
mRow = findViewById(R.id.row);
|
|
mTextColor = Utils.getColorAttr(mContext, R.attr.wallpaperTextColor);
|
|
mIconSize = (int) mContext.getResources().getDimension(R.dimen.widget_icon_size);
|
|
}
|
|
|
|
@Override
|
|
protected void onAttachedToWindow() {
|
|
super.onAttachedToWindow();
|
|
|
|
// Set initial content
|
|
showSlice(Slice.bindSlice(getContext(), mKeyguardSliceUri));
|
|
|
|
// Make sure we always have the most current slice
|
|
mLiveData.observeForever(this);
|
|
}
|
|
|
|
@Override
|
|
protected void onDetachedFromWindow() {
|
|
super.onDetachedFromWindow();
|
|
|
|
mLiveData.removeObserver(this);
|
|
}
|
|
|
|
private void showSlice(Slice slice) {
|
|
|
|
// Main area
|
|
SliceItem mainItem = SliceQuery.find(slice, android.app.slice.SliceItem.FORMAT_SLICE,
|
|
null /* hints */, new String[]{android.app.slice.Slice.HINT_LIST_ITEM});
|
|
mHasHeader = mainItem != null;
|
|
|
|
List<SliceItem> subItems = SliceQuery.findAll(slice,
|
|
android.app.slice.SliceItem.FORMAT_SLICE,
|
|
new String[]{android.app.slice.Slice.HINT_LIST_ITEM},
|
|
null /* nonHints */);
|
|
|
|
if (!mHasHeader) {
|
|
mTitle.setVisibility(GONE);
|
|
} else {
|
|
mTitle.setVisibility(VISIBLE);
|
|
SliceItem mainTitle = SliceQuery.find(mainItem.getSlice(),
|
|
android.app.slice.SliceItem.FORMAT_TEXT,
|
|
new String[]{android.app.slice.Slice.HINT_TITLE},
|
|
null /* nonHints */);
|
|
mTitle.setText(mainTitle.getText());
|
|
}
|
|
|
|
mClickActions.clear();
|
|
final int subItemsCount = subItems.size();
|
|
|
|
for (int i = 0; i < subItemsCount; i++) {
|
|
SliceItem item = subItems.get(i);
|
|
final Uri itemTag = item.getSlice().getUri();
|
|
// Try to reuse the view if already exists in the layout
|
|
KeyguardSliceButton button = mRow.findViewWithTag(itemTag);
|
|
if (button == null) {
|
|
button = new KeyguardSliceButton(mContext);
|
|
button.setTextColor(mTextColor);
|
|
button.setTag(itemTag);
|
|
} else {
|
|
mRow.removeView(button);
|
|
}
|
|
button.setHasDivider(i < subItemsCount - 1);
|
|
mRow.addView(button, i);
|
|
|
|
PendingIntent pendingIntent;
|
|
try {
|
|
pendingIntent = item.getAction();
|
|
} catch (RuntimeException e) {
|
|
Log.w(TAG, "Cannot retrieve action from keyguard slice", e);
|
|
pendingIntent = null;
|
|
}
|
|
mClickActions.put(button, pendingIntent);
|
|
|
|
SliceItem title = SliceQuery.find(item.getSlice(),
|
|
android.app.slice.SliceItem.FORMAT_TEXT,
|
|
new String[]{android.app.slice.Slice.HINT_TITLE},
|
|
null /* nonHints */);
|
|
button.setText(title.getText());
|
|
|
|
Drawable iconDrawable = null;
|
|
SliceItem icon = SliceQuery.find(item.getSlice(),
|
|
android.app.slice.SliceItem.FORMAT_IMAGE);
|
|
if (icon != null) {
|
|
iconDrawable = icon.getIcon().loadDrawable(mContext);
|
|
final int width = (int) (iconDrawable.getIntrinsicWidth()
|
|
/ (float) iconDrawable.getIntrinsicHeight() * mIconSize);
|
|
iconDrawable.setBounds(0, 0, Math.max(width, 1), mIconSize);
|
|
}
|
|
button.setCompoundDrawablesRelative(iconDrawable, null, null, null);
|
|
button.setOnClickListener(this);
|
|
}
|
|
|
|
// Removing old views
|
|
for (int i = 0; i < mRow.getChildCount(); i++) {
|
|
View child = mRow.getChildAt(i);
|
|
if (!mClickActions.containsKey(child)) {
|
|
mRow.removeView(child);
|
|
i--;
|
|
}
|
|
}
|
|
|
|
final int visibility = mHasHeader || subItemsCount > 0 ? VISIBLE : GONE;
|
|
if (visibility != getVisibility()) {
|
|
setVisibility(visibility);
|
|
}
|
|
|
|
mListener.accept(mHasHeader);
|
|
}
|
|
|
|
public void setDark(float darkAmount) {
|
|
mDarkAmount = darkAmount;
|
|
updateTextColors();
|
|
}
|
|
|
|
private void updateTextColors() {
|
|
final int blendedColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
|
|
mTitle.setTextColor(blendedColor);
|
|
int childCount = mRow.getChildCount();
|
|
for (int i = 0; i < childCount; i++) {
|
|
View v = mRow.getChildAt(i);
|
|
if (v instanceof Button) {
|
|
((Button) v).setTextColor(blendedColor);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onClick(View v) {
|
|
final PendingIntent action = mClickActions.get(v);
|
|
if (action != null) {
|
|
try {
|
|
action.send();
|
|
} catch (PendingIntent.CanceledException e) {
|
|
Log.i(TAG, "Pending intent cancelled, nothing to launch", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setListener(Consumer<Boolean> listener) {
|
|
mListener = listener;
|
|
}
|
|
|
|
public boolean hasHeader() {
|
|
return mHasHeader;
|
|
}
|
|
|
|
/**
|
|
* LiveData observer lifecycle.
|
|
* @param slice the new slice content.
|
|
*/
|
|
@Override
|
|
public void onChanged(Slice slice) {
|
|
showSlice(slice);
|
|
}
|
|
|
|
@Override
|
|
public void onTuningChanged(String key, String newValue) {
|
|
setupUri(newValue);
|
|
}
|
|
|
|
public void setupUri(String uriString) {
|
|
if (uriString == null) {
|
|
uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI;
|
|
}
|
|
|
|
boolean wasObserving = false;
|
|
if (mLiveData != null && mLiveData.hasActiveObservers()) {
|
|
wasObserving = true;
|
|
mLiveData.removeObserver(this);
|
|
}
|
|
|
|
mKeyguardSliceUri = Uri.parse(uriString);
|
|
mLiveData = SliceLiveData.fromUri(mContext, mKeyguardSliceUri);
|
|
|
|
if (wasObserving) {
|
|
mLiveData.observeForever(this);
|
|
showSlice(Slice.bindSlice(getContext(), mKeyguardSliceUri));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Representation of an item that appears under the clock on main keyguard message.
|
|
* Shows optional separator.
|
|
*/
|
|
private class KeyguardSliceButton extends Button {
|
|
|
|
private final Paint mPaint;
|
|
private boolean mHasDivider;
|
|
|
|
public KeyguardSliceButton(Context context) {
|
|
super(context, null /* attrs */,
|
|
com.android.keyguard.R.style.TextAppearance_Keyguard_Secondary);
|
|
mPaint = new Paint();
|
|
mPaint.setStyle(Paint.Style.STROKE);
|
|
float dividerWidth = context.getResources()
|
|
.getDimension(R.dimen.widget_separator_thickness);
|
|
mPaint.setStrokeWidth(dividerWidth);
|
|
int horizontalPadding = (int) context.getResources()
|
|
.getDimension(R.dimen.widget_horizontal_padding);
|
|
setPadding(horizontalPadding, 0, horizontalPadding, 0);
|
|
setCompoundDrawablePadding((int) context.getResources()
|
|
.getDimension(R.dimen.widget_icon_padding));
|
|
}
|
|
|
|
public void setHasDivider(boolean hasDivider) {
|
|
mHasDivider = hasDivider;
|
|
}
|
|
|
|
@Override
|
|
public void setTextColor(int color) {
|
|
super.setTextColor(color);
|
|
mPaint.setColor(color);
|
|
}
|
|
|
|
@Override
|
|
protected void onDraw(Canvas canvas) {
|
|
super.onDraw(canvas);
|
|
if (mHasDivider) {
|
|
final int lineX = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 0 : getWidth();
|
|
canvas.drawLine(lineX, 0, lineX, getHeight(), mPaint);
|
|
}
|
|
}
|
|
}
|
|
}
|