blob: b9bf80de304b2bc19a69331930d28a933936caab [file] [log] [blame]
Lucas Dupin957e50c2017-10-10 11:23:27 -07001/*
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
17package com.android.keyguard;
18
19import android.app.PendingIntent;
Lucas Dupin6bd86012017-12-05 17:58:57 -080020import android.arch.lifecycle.LiveData;
21import android.arch.lifecycle.Observer;
Lucas Dupin957e50c2017-10-10 11:23:27 -070022import android.content.Context;
Lucas Dupin6bd86012017-12-05 17:58:57 -080023import android.graphics.Canvas;
Lucas Dupin957e50c2017-10-10 11:23:27 -070024import android.graphics.Color;
Lucas Dupin6bd86012017-12-05 17:58:57 -080025import android.graphics.Paint;
26import android.graphics.drawable.Drawable;
Lucas Dupin957e50c2017-10-10 11:23:27 -070027import android.net.Uri;
Lucas Dupin6bd86012017-12-05 17:58:57 -080028import android.provider.Settings;
Lucas Dupin957e50c2017-10-10 11:23:27 -070029import android.util.AttributeSet;
Lucas Dupin6bd86012017-12-05 17:58:57 -080030import android.util.Log;
31import android.view.View;
32import android.widget.Button;
Lucas Dupin957e50c2017-10-10 11:23:27 -070033import android.widget.LinearLayout;
34import android.widget.TextView;
35
36import com.android.internal.graphics.ColorUtils;
Lucas Dupin6bd86012017-12-05 17:58:57 -080037import com.android.settingslib.Utils;
38import com.android.systemui.Dependency;
Lucas Dupin957e50c2017-10-10 11:23:27 -070039import com.android.systemui.R;
40import com.android.systemui.keyguard.KeyguardSliceProvider;
Lucas Dupin6bd86012017-12-05 17:58:57 -080041import com.android.systemui.tuner.TunerService;
Lucas Dupin957e50c2017-10-10 11:23:27 -070042
Lucas Dupin6bd86012017-12-05 17:58:57 -080043import java.util.HashMap;
44import java.util.List;
45import java.util.function.Consumer;
46
47import androidx.app.slice.Slice;
48import androidx.app.slice.SliceItem;
49import androidx.app.slice.core.SliceQuery;
50import androidx.app.slice.widget.SliceLiveData;
Jason Monk2af19982017-11-07 19:38:27 -050051
Lucas Dupin957e50c2017-10-10 11:23:27 -070052/**
53 * View visible under the clock on the lock screen and AoD.
54 */
Lucas Dupin6bd86012017-12-05 17:58:57 -080055public class KeyguardSliceView extends LinearLayout implements View.OnClickListener,
56 Observer<Slice>, TunerService.Tunable {
Lucas Dupin957e50c2017-10-10 11:23:27 -070057
Lucas Dupin6bd86012017-12-05 17:58:57 -080058 private static final String TAG = "KeyguardSliceView";
59 private final HashMap<View, PendingIntent> mClickActions;
60 private Uri mKeyguardSliceUri;
Lucas Dupin957e50c2017-10-10 11:23:27 -070061 private TextView mTitle;
Lucas Dupin6bd86012017-12-05 17:58:57 -080062 private LinearLayout mRow;
Lucas Dupin957e50c2017-10-10 11:23:27 -070063 private int mTextColor;
64 private float mDarkAmount = 0;
65
Lucas Dupin6bd86012017-12-05 17:58:57 -080066 private LiveData<Slice> mLiveData;
67 private int mIconSize;
68 private Consumer<Boolean> mListener;
69 private boolean mHasHeader;
Lucas Dupin957e50c2017-10-10 11:23:27 -070070
71 public KeyguardSliceView(Context context) {
72 this(context, null, 0);
73 }
74
75 public KeyguardSliceView(Context context, AttributeSet attrs) {
76 this(context, attrs, 0);
77 }
78
79 public KeyguardSliceView(Context context, AttributeSet attrs, int defStyle) {
80 super(context, attrs, defStyle);
Lucas Dupin6bd86012017-12-05 17:58:57 -080081
82 TunerService tunerService = Dependency.get(TunerService.class);
83 tunerService.addTunable(this, Settings.Secure.KEYGUARD_SLICE_URI);
84
85 mClickActions = new HashMap<>();
Lucas Dupin957e50c2017-10-10 11:23:27 -070086 }
87
88 @Override
89 protected void onFinishInflate() {
90 super.onFinishInflate();
91 mTitle = findViewById(R.id.title);
Lucas Dupin6bd86012017-12-05 17:58:57 -080092 mRow = findViewById(R.id.row);
93 mTextColor = Utils.getColorAttr(mContext, R.attr.wallpaperTextColor);
94 mIconSize = (int) mContext.getResources().getDimension(R.dimen.widget_icon_size);
Lucas Dupin957e50c2017-10-10 11:23:27 -070095 }
96
97 @Override
98 protected void onAttachedToWindow() {
99 super.onAttachedToWindow();
100
101 // Set initial content
Lucas Dupin6bd86012017-12-05 17:58:57 -0800102 showSlice(Slice.bindSlice(getContext(), mKeyguardSliceUri));
Lucas Dupin957e50c2017-10-10 11:23:27 -0700103
104 // Make sure we always have the most current slice
Lucas Dupin6bd86012017-12-05 17:58:57 -0800105 mLiveData.observeForever(this);
Lucas Dupin957e50c2017-10-10 11:23:27 -0700106 }
107
108 @Override
109 protected void onDetachedFromWindow() {
110 super.onDetachedFromWindow();
111
Lucas Dupin6bd86012017-12-05 17:58:57 -0800112 mLiveData.removeObserver(this);
Lucas Dupin957e50c2017-10-10 11:23:27 -0700113 }
114
115 private void showSlice(Slice slice) {
Lucas Dupin957e50c2017-10-10 11:23:27 -0700116
Lucas Dupin6bd86012017-12-05 17:58:57 -0800117 // Main area
118 SliceItem mainItem = SliceQuery.find(slice, android.app.slice.SliceItem.FORMAT_SLICE,
119 null /* hints */, new String[]{android.app.slice.Slice.HINT_LIST_ITEM});
120 mHasHeader = mainItem != null;
Lucas Dupin957e50c2017-10-10 11:23:27 -0700121
Lucas Dupin6bd86012017-12-05 17:58:57 -0800122 List<SliceItem> subItems = SliceQuery.findAll(slice,
123 android.app.slice.SliceItem.FORMAT_SLICE,
124 new String[]{android.app.slice.Slice.HINT_LIST_ITEM},
125 null /* nonHints */);
126
127 if (!mHasHeader) {
Lucas Dupin957e50c2017-10-10 11:23:27 -0700128 mTitle.setVisibility(GONE);
129 } else {
130 mTitle.setVisibility(VISIBLE);
Lucas Dupin6bd86012017-12-05 17:58:57 -0800131 SliceItem mainTitle = SliceQuery.find(mainItem.getSlice(),
132 android.app.slice.SliceItem.FORMAT_TEXT,
133 new String[]{android.app.slice.Slice.HINT_TITLE},
134 null /* nonHints */);
135 mTitle.setText(mainTitle.getText());
Lucas Dupin957e50c2017-10-10 11:23:27 -0700136 }
137
Lucas Dupin6bd86012017-12-05 17:58:57 -0800138 mClickActions.clear();
139 final int subItemsCount = subItems.size();
140
141 for (int i = 0; i < subItemsCount; i++) {
142 SliceItem item = subItems.get(i);
143 final Uri itemTag = item.getSlice().getUri();
144 // Try to reuse the view if already exists in the layout
145 KeyguardSliceButton button = mRow.findViewWithTag(itemTag);
146 if (button == null) {
147 button = new KeyguardSliceButton(mContext);
148 button.setTextColor(mTextColor);
149 button.setTag(itemTag);
150 } else {
151 mRow.removeView(button);
152 }
153 button.setHasDivider(i < subItemsCount - 1);
154 mRow.addView(button, i);
155
156 PendingIntent pendingIntent;
157 try {
158 pendingIntent = item.getAction();
159 } catch (RuntimeException e) {
160 Log.w(TAG, "Cannot retrieve action from keyguard slice", e);
161 pendingIntent = null;
162 }
163 mClickActions.put(button, pendingIntent);
164
165 SliceItem title = SliceQuery.find(item.getSlice(),
166 android.app.slice.SliceItem.FORMAT_TEXT,
167 new String[]{android.app.slice.Slice.HINT_TITLE},
168 null /* nonHints */);
169 button.setText(title.getText());
170
171 Drawable iconDrawable = null;
172 SliceItem icon = SliceQuery.find(item.getSlice(),
173 android.app.slice.SliceItem.FORMAT_IMAGE);
174 if (icon != null) {
175 iconDrawable = icon.getIcon().loadDrawable(mContext);
176 final int width = (int) (iconDrawable.getIntrinsicWidth()
177 / (float) iconDrawable.getIntrinsicHeight() * mIconSize);
178 iconDrawable.setBounds(0, 0, Math.max(width, 1), mIconSize);
179 }
180 button.setCompoundDrawablesRelative(iconDrawable, null, null, null);
181 button.setOnClickListener(this);
Lucas Dupin957e50c2017-10-10 11:23:27 -0700182 }
183
Lucas Dupin6bd86012017-12-05 17:58:57 -0800184 // Removing old views
185 for (int i = 0; i < mRow.getChildCount(); i++) {
186 View child = mRow.getChildAt(i);
187 if (!mClickActions.containsKey(child)) {
188 mRow.removeView(child);
189 i--;
190 }
191 }
192
193 final int visibility = mHasHeader || subItemsCount > 0 ? VISIBLE : GONE;
Lucas Dupin957e50c2017-10-10 11:23:27 -0700194 if (visibility != getVisibility()) {
195 setVisibility(visibility);
196 }
Lucas Dupin6bd86012017-12-05 17:58:57 -0800197
198 mListener.accept(mHasHeader);
Lucas Dupin957e50c2017-10-10 11:23:27 -0700199 }
200
201 public void setDark(float darkAmount) {
202 mDarkAmount = darkAmount;
203 updateTextColors();
204 }
205
Lucas Dupin957e50c2017-10-10 11:23:27 -0700206 private void updateTextColors() {
207 final int blendedColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
208 mTitle.setTextColor(blendedColor);
Lucas Dupin6bd86012017-12-05 17:58:57 -0800209 int childCount = mRow.getChildCount();
210 for (int i = 0; i < childCount; i++) {
211 View v = mRow.getChildAt(i);
212 if (v instanceof Button) {
213 ((Button) v).setTextColor(blendedColor);
214 }
215 }
Lucas Dupin957e50c2017-10-10 11:23:27 -0700216 }
217
Lucas Dupin6bd86012017-12-05 17:58:57 -0800218 @Override
219 public void onClick(View v) {
220 final PendingIntent action = mClickActions.get(v);
221 if (action != null) {
222 try {
223 action.send();
224 } catch (PendingIntent.CanceledException e) {
225 Log.i(TAG, "Pending intent cancelled, nothing to launch", e);
226 }
227 }
228 }
229
230 public void setListener(Consumer<Boolean> listener) {
231 mListener = listener;
232 }
233
234 public boolean hasHeader() {
235 return mHasHeader;
236 }
237
238 /**
239 * LiveData observer lifecycle.
240 * @param slice the new slice content.
241 */
242 @Override
243 public void onChanged(Slice slice) {
244 showSlice(slice);
245 }
246
247 @Override
248 public void onTuningChanged(String key, String newValue) {
249 setupUri(newValue);
250 }
251
252 public void setupUri(String uriString) {
253 if (uriString == null) {
254 uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI;
255 }
256
257 boolean wasObserving = false;
258 if (mLiveData != null && mLiveData.hasActiveObservers()) {
259 wasObserving = true;
260 mLiveData.removeObserver(this);
261 }
262
263 mKeyguardSliceUri = Uri.parse(uriString);
264 mLiveData = SliceLiveData.fromUri(mContext, mKeyguardSliceUri);
265
266 if (wasObserving) {
267 mLiveData.observeForever(this);
268 showSlice(Slice.bindSlice(getContext(), mKeyguardSliceUri));
269 }
270 }
271
272 /**
273 * Representation of an item that appears under the clock on main keyguard message.
274 * Shows optional separator.
275 */
276 private class KeyguardSliceButton extends Button {
277
278 private final Paint mPaint;
279 private boolean mHasDivider;
280
281 public KeyguardSliceButton(Context context) {
282 super(context, null /* attrs */,
283 com.android.keyguard.R.style.TextAppearance_Keyguard_Secondary);
284 mPaint = new Paint();
285 mPaint.setStyle(Paint.Style.STROKE);
286 float dividerWidth = context.getResources()
287 .getDimension(R.dimen.widget_separator_thickness);
288 mPaint.setStrokeWidth(dividerWidth);
289 int horizontalPadding = (int) context.getResources()
290 .getDimension(R.dimen.widget_horizontal_padding);
291 setPadding(horizontalPadding, 0, horizontalPadding, 0);
292 setCompoundDrawablePadding((int) context.getResources()
293 .getDimension(R.dimen.widget_icon_padding));
294 }
295
296 public void setHasDivider(boolean hasDivider) {
297 mHasDivider = hasDivider;
Lucas Dupin957e50c2017-10-10 11:23:27 -0700298 }
299
300 @Override
Lucas Dupin6bd86012017-12-05 17:58:57 -0800301 public void setTextColor(int color) {
302 super.setTextColor(color);
303 mPaint.setColor(color);
Lucas Dupin957e50c2017-10-10 11:23:27 -0700304 }
305
306 @Override
Lucas Dupin6bd86012017-12-05 17:58:57 -0800307 protected void onDraw(Canvas canvas) {
308 super.onDraw(canvas);
309 if (mHasDivider) {
310 final int lineX = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 0 : getWidth();
311 canvas.drawLine(lineX, 0, lineX, getHeight(), mPaint);
312 }
Lucas Dupin957e50c2017-10-10 11:23:27 -0700313 }
314 }
315}