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