• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.systemui.qs;
18 
19 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
20 import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.content.ComponentName;
25 import android.content.res.Configuration;
26 import android.metrics.LogMaker;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.internal.logging.MetricsLogger;
30 import com.android.internal.logging.UiEventLogger;
31 import com.android.systemui.Dumpable;
32 import com.android.systemui.dump.DumpManager;
33 import com.android.systemui.media.MediaHost;
34 import com.android.systemui.plugins.qs.QSTile;
35 import com.android.systemui.plugins.qs.QSTileView;
36 import com.android.systemui.qs.customize.QSCustomizerController;
37 import com.android.systemui.qs.external.CustomTile;
38 import com.android.systemui.qs.logging.QSLogger;
39 import com.android.systemui.statusbar.FeatureFlags;
40 import com.android.systemui.util.Utils;
41 import com.android.systemui.util.ViewController;
42 import com.android.systemui.util.animation.DisappearParameters;
43 
44 import java.io.FileDescriptor;
45 import java.io.PrintWriter;
46 import java.util.ArrayList;
47 import java.util.Collection;
48 import java.util.function.Consumer;
49 import java.util.stream.Collectors;
50 
51 import javax.inject.Named;
52 
53 import kotlin.Unit;
54 import kotlin.jvm.functions.Function1;
55 
56 /**
57  * Controller for QSPanel views.
58  *
59  * @param <T> Type of QSPanel.
60  */
61 public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewController<T>
62         implements Dumpable{
63     protected final QSTileHost mHost;
64     private final QSCustomizerController mQsCustomizerController;
65     private final boolean mUsingMediaPlayer;
66     protected final MediaHost mMediaHost;
67     protected final MetricsLogger mMetricsLogger;
68     private final UiEventLogger mUiEventLogger;
69     private final QSLogger mQSLogger;
70     private final DumpManager mDumpManager;
71     private final FeatureFlags mFeatureFlags;
72     protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
73     private boolean mShouldUseSplitNotificationShade;
74 
75     @Nullable
76     private Consumer<Boolean> mMediaVisibilityChangedListener;
77     private int mLastOrientation;
78     private String mCachedSpecs = "";
79     private QSTileRevealController mQsTileRevealController;
80     private float mRevealExpansion;
81 
82     private final QSHost.Callback mQSHostCallback = this::setTiles;
83 
84     @VisibleForTesting
85     protected final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
86             new QSPanel.OnConfigurationChangedListener() {
87                 @Override
88                 public void onConfigurationChange(Configuration newConfig) {
89                     mShouldUseSplitNotificationShade =
90                             Utils.shouldUseSplitNotificationShade(mFeatureFlags, getResources());
91                     if (newConfig.orientation != mLastOrientation) {
92                         mLastOrientation = newConfig.orientation;
93                         switchTileLayout(false);
94                     }
95                 }
96             };
97 
98     private final Function1<Boolean, Unit> mMediaHostVisibilityListener = (visible) -> {
99         if (mMediaVisibilityChangedListener != null) {
100             mMediaVisibilityChangedListener.accept(visible);
101         }
102         switchTileLayout(false);
103         return null;
104     };
105 
106     private boolean mUsingHorizontalLayout;
107 
108     @Nullable
109     private Runnable mUsingHorizontalLayoutChangedListener;
110 
QSPanelControllerBase( T view, QSTileHost host, QSCustomizerController qsCustomizerController, @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer, MediaHost mediaHost, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, DumpManager dumpManager, FeatureFlags featureFlags )111     protected QSPanelControllerBase(
112             T view,
113             QSTileHost host,
114             QSCustomizerController qsCustomizerController,
115             @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
116             MediaHost mediaHost,
117             MetricsLogger metricsLogger,
118             UiEventLogger uiEventLogger,
119             QSLogger qsLogger,
120             DumpManager dumpManager,
121             FeatureFlags featureFlags
122     ) {
123         super(view);
124         mHost = host;
125         mQsCustomizerController = qsCustomizerController;
126         mUsingMediaPlayer = usingMediaPlayer;
127         mMediaHost = mediaHost;
128         mMetricsLogger = metricsLogger;
129         mUiEventLogger = uiEventLogger;
130         mQSLogger = qsLogger;
131         mDumpManager = dumpManager;
132         mFeatureFlags = featureFlags;
133         mShouldUseSplitNotificationShade =
134                 Utils.shouldUseSplitNotificationShade(mFeatureFlags, getResources());
135     }
136 
137     @Override
onInit()138     protected void onInit() {
139         mView.initialize();
140         mQSLogger.logAllTilesChangeListening(mView.isListening(), mView.getDumpableTag(), "");
141     }
142 
143     /**
144      * @return the media host for this panel
145      */
getMediaHost()146     public MediaHost getMediaHost() {
147         return mMediaHost;
148     }
149 
150     @Override
onViewAttached()151     protected void onViewAttached() {
152         mQsTileRevealController = createTileRevealController();
153         if (mQsTileRevealController != null) {
154             mQsTileRevealController.setExpansion(mRevealExpansion);
155         }
156 
157         mMediaHost.addVisibilityChangeListener(mMediaHostVisibilityListener);
158         mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
159         mHost.addCallback(mQSHostCallback);
160         setTiles();
161         mLastOrientation = getResources().getConfiguration().orientation;
162         switchTileLayout(true);
163 
164         mDumpManager.registerDumpable(mView.getDumpableTag(), this);
165     }
166 
167     @Override
onViewDetached()168     protected void onViewDetached() {
169         mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener);
170         mHost.removeCallback(mQSHostCallback);
171 
172         mView.getTileLayout().setListening(false, mUiEventLogger);
173 
174         mMediaHost.removeVisibilityChangeListener(mMediaHostVisibilityListener);
175 
176         for (TileRecord record : mRecords) {
177             record.tile.removeCallbacks();
178         }
179         mRecords.clear();
180         mDumpManager.unregisterDumpable(mView.getDumpableTag());
181     }
182 
createTileRevealController()183     protected QSTileRevealController createTileRevealController() {
184         return null;
185     }
186 
187     /** */
setTiles()188     public void setTiles() {
189         setTiles(mHost.getTiles(), false);
190     }
191 
192     /** */
setTiles(Collection<QSTile> tiles, boolean collapsedView)193     public void setTiles(Collection<QSTile> tiles, boolean collapsedView) {
194         // TODO(b/168904199): move this logic into QSPanelController.
195         if (!collapsedView && mQsTileRevealController != null) {
196             mQsTileRevealController.updateRevealedTiles(tiles);
197         }
198 
199         for (QSPanelControllerBase.TileRecord record : mRecords) {
200             mView.removeTile(record);
201             record.tile.removeCallback(record.callback);
202         }
203         mRecords.clear();
204         mCachedSpecs = "";
205         for (QSTile tile : tiles) {
206             addTile(tile, collapsedView);
207         }
208     }
209 
210     /** */
refreshAllTiles()211     public void refreshAllTiles() {
212         for (QSPanelControllerBase.TileRecord r : mRecords) {
213             r.tile.refreshState();
214         }
215     }
216 
addTile(final QSTile tile, boolean collapsedView)217     private void addTile(final QSTile tile, boolean collapsedView) {
218         final TileRecord r = new TileRecord();
219         r.tile = tile;
220         r.tileView = mHost.createTileView(getContext(), tile, collapsedView);
221         mView.addTile(r);
222         mRecords.add(r);
223         mCachedSpecs = getTilesSpecs();
224     }
225 
226     /** */
clickTile(ComponentName tile)227     public void clickTile(ComponentName tile) {
228         final String spec = CustomTile.toSpec(tile);
229         for (TileRecord record : mRecords) {
230             if (record.tile.getTileSpec().equals(spec)) {
231                 record.tile.click(null /* view */);
232                 break;
233             }
234         }
235     }
getTile(String subPanel)236     protected QSTile getTile(String subPanel) {
237         for (int i = 0; i < mRecords.size(); i++) {
238             if (subPanel.equals(mRecords.get(i).tile.getTileSpec())) {
239                 return mRecords.get(i).tile;
240             }
241         }
242         return mHost.createTile(subPanel);
243     }
244 
areThereTiles()245     boolean areThereTiles() {
246         return !mRecords.isEmpty();
247     }
248 
getTileView(QSTile tile)249     QSTileView getTileView(QSTile tile) {
250         for (QSPanelControllerBase.TileRecord r : mRecords) {
251             if (r.tile == tile) {
252                 return r.tileView;
253             }
254         }
255         return null;
256     }
257 
getTilesSpecs()258     private String getTilesSpecs() {
259         return mRecords.stream()
260                 .map(tileRecord ->  tileRecord.tile.getTileSpec())
261                 .collect(Collectors.joining(","));
262     }
263 
264     /** */
setExpanded(boolean expanded)265     public void setExpanded(boolean expanded) {
266         if (mView.isExpanded() == expanded) {
267             return;
268         }
269         mQSLogger.logPanelExpanded(expanded, mView.getDumpableTag());
270 
271         mView.setExpanded(expanded);
272         mMetricsLogger.visibility(MetricsEvent.QS_PANEL, expanded);
273         if (!expanded) {
274             mUiEventLogger.log(mView.closePanelEvent());
275             closeDetail();
276         } else {
277             mUiEventLogger.log(mView.openPanelEvent());
278             logTiles();
279         }
280     }
281 
282     /** */
closeDetail()283     public void closeDetail() {
284         if (mQsCustomizerController.isShown()) {
285             mQsCustomizerController.hide();
286             return;
287         }
288         mView.closeDetail();
289     }
290 
291     /** */
openDetails(String subPanel)292     public void openDetails(String subPanel) {
293         QSTile tile = getTile(subPanel);
294         // If there's no tile with that name (as defined in QSFactoryImpl or other QSFactory),
295         // QSFactory will not be able to create a tile and getTile will return null
296         if (tile != null) {
297             mView.showDetailAdapter(
298                     true, tile.getDetailAdapter(), new int[]{mView.getWidth() / 2, 0});
299         }
300     }
301 
302 
setListening(boolean listening)303     void setListening(boolean listening) {
304         mView.setListening(listening);
305 
306         if (mView.getTileLayout() != null) {
307             mQSLogger.logAllTilesChangeListening(listening, mView.getDumpableTag(), mCachedSpecs);
308             mView.getTileLayout().setListening(listening, mUiEventLogger);
309         }
310     }
311 
switchTileLayout(boolean force)312     boolean switchTileLayout(boolean force) {
313         /* Whether or not the panel currently contains a media player. */
314         boolean horizontal = shouldUseHorizontalLayout();
315         if (horizontal != mUsingHorizontalLayout || force) {
316             mUsingHorizontalLayout = horizontal;
317             mView.setUsingHorizontalLayout(mUsingHorizontalLayout, mMediaHost.getHostView(), force);
318             updateMediaDisappearParameters();
319             if (mUsingHorizontalLayoutChangedListener != null) {
320                 mUsingHorizontalLayoutChangedListener.run();
321             }
322             return true;
323         }
324         return false;
325     }
326 
327     /**
328      * Update the way the media disappears based on if we're using the horizontal layout
329      */
updateMediaDisappearParameters()330     void updateMediaDisappearParameters() {
331         if (!mUsingMediaPlayer) {
332             return;
333         }
334         DisappearParameters parameters = mMediaHost.getDisappearParameters();
335         if (mUsingHorizontalLayout) {
336             // Only height remaining
337             parameters.getDisappearSize().set(0.0f, 0.4f);
338             // Disappearing on the right side on the bottom
339             parameters.getGonePivot().set(1.0f, 1.0f);
340             // translating a bit horizontal
341             parameters.getContentTranslationFraction().set(0.25f, 1.0f);
342             parameters.setDisappearEnd(0.6f);
343         } else {
344             // Only width remaining
345             parameters.getDisappearSize().set(1.0f, 0.0f);
346             // Disappearing on the bottom
347             parameters.getGonePivot().set(0.0f, 1.0f);
348             // translating a bit vertical
349             parameters.getContentTranslationFraction().set(0.0f, 1.05f);
350             parameters.setDisappearEnd(0.95f);
351         }
352         parameters.setFadeStartPosition(0.95f);
353         parameters.setDisappearStart(0.0f);
354         mMediaHost.setDisappearParameters(parameters);
355     }
356 
shouldUseHorizontalLayout()357     boolean shouldUseHorizontalLayout() {
358         if (mShouldUseSplitNotificationShade)  {
359             return false;
360         }
361         return mUsingMediaPlayer && mMediaHost.getVisible()
362                 && mLastOrientation == Configuration.ORIENTATION_LANDSCAPE;
363     }
364 
logTiles()365     private void logTiles() {
366         for (int i = 0; i < mRecords.size(); i++) {
367             QSTile tile = mRecords.get(i).tile;
368             mMetricsLogger.write(tile.populate(new LogMaker(tile.getMetricsCategory())
369                     .setType(MetricsEvent.TYPE_OPEN)));
370         }
371     }
372 
373     /** Set the expansion on the associated {@link QSTileRevealController}. */
setRevealExpansion(float expansion)374     public void setRevealExpansion(float expansion) {
375         mRevealExpansion = expansion;
376         if (mQsTileRevealController != null) {
377             mQsTileRevealController.setExpansion(expansion);
378         }
379     }
380 
381     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)382     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
383         pw.println(getClass().getSimpleName() + ":");
384         pw.println("  Tile records:");
385         for (QSPanelControllerBase.TileRecord record : mRecords) {
386             if (record.tile instanceof Dumpable) {
387                 pw.print("    "); ((Dumpable) record.tile).dump(fd, pw, args);
388                 pw.print("    "); pw.println(record.tileView.toString());
389             }
390         }
391     }
392 
getTileLayout()393     public QSPanel.QSTileLayout getTileLayout() {
394         return mView.getTileLayout();
395     }
396 
397     /**
398      * Add a listener for when the media visibility changes.
399      */
setMediaVisibilityChangedListener(@onNull Consumer<Boolean> listener)400     public void setMediaVisibilityChangedListener(@NonNull Consumer<Boolean> listener) {
401         mMediaVisibilityChangedListener = listener;
402     }
403 
404     /**
405      * Add a listener when the horizontal layout changes
406      */
setUsingHorizontalLayoutChangeListener(Runnable listener)407     public void setUsingHorizontalLayoutChangeListener(Runnable listener) {
408         mUsingHorizontalLayoutChangedListener = listener;
409     }
410 
411     /** */
412     public static final class TileRecord extends QSPanel.Record {
413         public QSTile tile;
414         public com.android.systemui.plugins.qs.QSTileView tileView;
415         public boolean scanState;
416         public QSTile.Callback callback;
417     }
418 }
419