1 /*
2  * Copyright (C) 2020 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 static android.app.slice.Slice.HINT_LIST_ITEM;
20 import static android.view.Display.DEFAULT_DISPLAY;
21 
22 import android.app.PendingIntent;
23 import android.net.Uri;
24 import android.os.Trace;
25 import android.provider.Settings;
26 import android.util.Log;
27 import android.view.Display;
28 import android.view.View;
29 import android.view.ViewGroup;
30 
31 import androidx.annotation.NonNull;
32 import androidx.lifecycle.LiveData;
33 import androidx.lifecycle.Observer;
34 import androidx.slice.Slice;
35 import androidx.slice.SliceViewManager;
36 import androidx.slice.widget.ListContent;
37 import androidx.slice.widget.RowContent;
38 import androidx.slice.widget.SliceContent;
39 import androidx.slice.widget.SliceLiveData;
40 
41 import com.android.keyguard.dagger.KeyguardStatusViewScope;
42 import com.android.systemui.Dumpable;
43 import com.android.systemui.dump.DumpManager;
44 import com.android.systemui.keyguard.KeyguardSliceProvider;
45 import com.android.systemui.plugins.ActivityStarter;
46 import com.android.systemui.statusbar.policy.ConfigurationController;
47 import com.android.systemui.tuner.TunerService;
48 import com.android.systemui.util.ViewController;
49 
50 import java.io.FileDescriptor;
51 import java.io.PrintWriter;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.stream.Collectors;
55 
56 import javax.inject.Inject;
57 
58 /** Controller for a {@link KeyguardSliceView}. */
59 @KeyguardStatusViewScope
60 public class KeyguardSliceViewController extends ViewController<KeyguardSliceView> implements
61         Dumpable {
62     private static final String TAG = "KeyguardSliceViewCtrl";
63 
64     private final ActivityStarter mActivityStarter;
65     private final ConfigurationController mConfigurationController;
66     private final TunerService mTunerService;
67     private final DumpManager mDumpManager;
68     private int mDisplayId;
69     private LiveData<Slice> mLiveData;
70     private Uri mKeyguardSliceUri;
71     private Slice mSlice;
72     private Map<View, PendingIntent> mClickActions;
73 
74     TunerService.Tunable mTunable = (key, newValue) -> setupUri(newValue);
75 
76     ConfigurationController.ConfigurationListener mConfigurationListener =
77             new ConfigurationController.ConfigurationListener() {
78         @Override
79         public void onDensityOrFontScaleChanged() {
80             mView.onDensityOrFontScaleChanged();
81         }
82         @Override
83         public void onThemeChanged() {
84             mView.onOverlayChanged();
85         }
86     };
87 
88     Observer<Slice> mObserver = new Observer<Slice>() {
89         @Override
90         public void onChanged(Slice slice) {
91             mSlice = slice;
92             showSlice(slice);
93         }
94     };
95 
96     private View.OnClickListener mOnClickListener = new View.OnClickListener() {
97         @Override
98         public void onClick(View v) {
99             final PendingIntent action = mClickActions.get(v);
100             if (action != null && mActivityStarter != null) {
101                 mActivityStarter.startPendingIntentDismissingKeyguard(action);
102             }
103         }
104     };
105 
106     @Inject
KeyguardSliceViewController( KeyguardSliceView keyguardSliceView, ActivityStarter activityStarter, ConfigurationController configurationController, TunerService tunerService, DumpManager dumpManager)107     public KeyguardSliceViewController(
108             KeyguardSliceView keyguardSliceView,
109             ActivityStarter activityStarter,
110             ConfigurationController configurationController,
111             TunerService tunerService,
112             DumpManager dumpManager) {
113         super(keyguardSliceView);
114         mActivityStarter = activityStarter;
115         mConfigurationController = configurationController;
116         mTunerService = tunerService;
117         mDumpManager = dumpManager;
118     }
119 
120     @Override
onViewAttached()121     protected void onViewAttached() {
122         Display display = mView.getDisplay();
123         if (display != null) {
124             mDisplayId = display.getDisplayId();
125         }
126         mTunerService.addTunable(mTunable, Settings.Secure.KEYGUARD_SLICE_URI);
127         // Make sure we always have the most current slice
128         if (mDisplayId == DEFAULT_DISPLAY && mLiveData != null) {
129             mLiveData.observeForever(mObserver);
130         }
131         mConfigurationController.addCallback(mConfigurationListener);
132         mDumpManager.registerDumpable(
133                 TAG + "@" + Integer.toHexString(
134                         KeyguardSliceViewController.this.hashCode()),
135                 KeyguardSliceViewController.this);
136     }
137 
138     @Override
onViewDetached()139     protected void onViewDetached() {
140         // TODO(b/117344873) Remove below work around after this issue be fixed.
141         if (mDisplayId == DEFAULT_DISPLAY) {
142             mLiveData.removeObserver(mObserver);
143         }
144         mTunerService.removeTunable(mTunable);
145         mConfigurationController.removeCallback(mConfigurationListener);
146         mDumpManager.unregisterDumpable(
147                 TAG + "@" + Integer.toHexString(
148                         KeyguardSliceViewController.this.hashCode()));
149     }
150 
updateTopMargin(float clockTopTextPadding)151     void updateTopMargin(float clockTopTextPadding) {
152         ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) mView.getLayoutParams();
153         lp.topMargin = (int) clockTopTextPadding;
154         mView.setLayoutParams(lp);
155     }
156 
157     /**
158      * Sets the slice provider Uri.
159      */
setupUri(String uriString)160     public void setupUri(String uriString) {
161         if (uriString == null) {
162             uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI;
163         }
164 
165         boolean wasObserving = false;
166         if (mLiveData != null && mLiveData.hasActiveObservers()) {
167             wasObserving = true;
168             mLiveData.removeObserver(mObserver);
169         }
170 
171         mKeyguardSliceUri = Uri.parse(uriString);
172         mLiveData = SliceLiveData.fromUri(mView.getContext(), mKeyguardSliceUri);
173 
174         if (wasObserving) {
175             mLiveData.observeForever(mObserver);
176         }
177     }
178 
179     /**
180      * Update contents of the view.
181      */
refresh()182     public void refresh() {
183         Slice slice;
184         Trace.beginSection("KeyguardSliceViewController#refresh");
185         // We can optimize performance and avoid binder calls when we know that we're bound
186         // to a Slice on the same process.
187         if (KeyguardSliceProvider.KEYGUARD_SLICE_URI.equals(mKeyguardSliceUri.toString())) {
188             KeyguardSliceProvider instance = KeyguardSliceProvider.getAttachedInstance();
189             if (instance != null) {
190                 slice = instance.onBindSlice(mKeyguardSliceUri);
191             } else {
192                 Log.w(TAG, "Keyguard slice not bound yet?");
193                 slice = null;
194             }
195         } else {
196             // TODO: Make SliceViewManager injectable
197             slice = SliceViewManager.getInstance(mView.getContext()).bindSlice(mKeyguardSliceUri);
198         }
199         mObserver.onChanged(slice);
200         Trace.endSection();
201     }
202 
showSlice(Slice slice)203     void showSlice(Slice slice) {
204         Trace.beginSection("KeyguardSliceViewController#showSlice");
205         if (slice == null) {
206             mView.hideSlice();
207             Trace.endSection();
208             return;
209         }
210 
211         ListContent lc = new ListContent(slice);
212         RowContent headerContent = lc.getHeader();
213         boolean hasHeader =
214                 headerContent != null && !headerContent.getSliceItem().hasHint(HINT_LIST_ITEM);
215 
216         List<SliceContent> subItems = lc.getRowItems().stream().filter(sliceContent -> {
217             String itemUri = sliceContent.getSliceItem().getSlice().getUri().toString();
218             // Filter out the action row
219             return !KeyguardSliceProvider.KEYGUARD_ACTION_URI.equals(itemUri);
220         }).collect(Collectors.toList());
221 
222 
223         mClickActions = mView.showSlice(hasHeader ? headerContent : null, subItems);
224 
225         Trace.endSection();
226     }
227 
228     @Override
dump(@onNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args)229     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
230         pw.println("  mSlice: " + mSlice);
231         pw.println("  mClickActions: " + mClickActions);
232     }
233 }
234