1 /*
2  * Copyright (C) 2022 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.tiles;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.content.pm.PackageManager;
25 import android.content.pm.ServiceInfo;
26 import android.os.Build;
27 import android.os.Handler;
28 import android.os.Looper;
29 import android.os.RemoteException;
30 import android.provider.Settings;
31 import android.service.dreams.IDreamManager;
32 import android.service.quicksettings.Tile;
33 import android.text.TextUtils;
34 import android.util.Log;
35 import android.view.View;
36 
37 import androidx.annotation.Nullable;
38 import androidx.annotation.VisibleForTesting;
39 
40 import com.android.internal.logging.MetricsLogger;
41 import com.android.systemui.R;
42 import com.android.systemui.broadcast.BroadcastDispatcher;
43 import com.android.systemui.dagger.qualifiers.Background;
44 import com.android.systemui.dagger.qualifiers.Main;
45 import com.android.systemui.dreams.dagger.DreamModule;
46 import com.android.systemui.plugins.ActivityStarter;
47 import com.android.systemui.plugins.FalsingManager;
48 import com.android.systemui.plugins.qs.QSTile;
49 import com.android.systemui.plugins.statusbar.StatusBarStateController;
50 import com.android.systemui.qs.QSHost;
51 import com.android.systemui.qs.QsEventLogger;
52 import com.android.systemui.qs.SettingObserver;
53 import com.android.systemui.qs.logging.QSLogger;
54 import com.android.systemui.qs.tileimpl.QSTileImpl;
55 import com.android.systemui.settings.UserTracker;
56 import com.android.systemui.util.settings.SecureSettings;
57 
58 import javax.inject.Inject;
59 import javax.inject.Named;
60 
61 /** Quick settings tile: Screensaver (dream) **/
62 public class DreamTile extends QSTileImpl<QSTile.BooleanState> {
63 
64     public static final String TILE_SPEC = "dream";
65 
66     private static final String LOG_TAG = "QSDream";
67     // TODO: consider 1 animated icon instead
68     private final Icon mIconDocked = ResourceIcon.get(R.drawable.ic_qs_screen_saver);
69     private final Icon mIconUndocked = ResourceIcon.get(R.drawable.ic_qs_screen_saver_undocked);
70     private final IDreamManager mDreamManager;
71     private final BroadcastDispatcher mBroadcastDispatcher;
72     private final SettingObserver mEnabledSettingObserver;
73     private final SettingObserver mDreamSettingObserver;
74     private final UserTracker mUserTracker;
75     private final boolean mDreamSupported;
76     private final boolean mDreamOnlyEnabledForDockUser;
77 
78     private boolean mIsDocked = false;
79 
80     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
81         @Override
82         public void onReceive(Context context, Intent intent) {
83             if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction())) {
84                 mIsDocked = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, -1)
85                         != Intent.EXTRA_DOCK_STATE_UNDOCKED;
86             }
87             refreshState();
88         }
89     };
90 
91     @Inject
DreamTile( QSHost host, QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, IDreamManager dreamManager, SecureSettings secureSettings, BroadcastDispatcher broadcastDispatcher, UserTracker userTracker, @Named(DreamModule.DREAM_SUPPORTED) boolean dreamSupported, @Named(DreamModule.DREAM_ONLY_ENABLED_FOR_DOCK_USER) boolean dreamOnlyEnabledForDockUser )92     public DreamTile(
93             QSHost host,
94             QsEventLogger uiEventLogger,
95             @Background Looper backgroundLooper,
96             @Main Handler mainHandler,
97             FalsingManager falsingManager,
98             MetricsLogger metricsLogger,
99             StatusBarStateController statusBarStateController,
100             ActivityStarter activityStarter,
101             QSLogger qsLogger,
102             IDreamManager dreamManager,
103             SecureSettings secureSettings,
104             BroadcastDispatcher broadcastDispatcher,
105             UserTracker userTracker,
106             @Named(DreamModule.DREAM_SUPPORTED) boolean dreamSupported,
107             @Named(DreamModule.DREAM_ONLY_ENABLED_FOR_DOCK_USER)
108                     boolean dreamOnlyEnabledForDockUser
109     ) {
110         super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
111                 statusBarStateController, activityStarter, qsLogger);
112         mDreamManager = dreamManager;
113         mBroadcastDispatcher = broadcastDispatcher;
114         mEnabledSettingObserver = new SettingObserver(secureSettings, mHandler,
115                 Settings.Secure.SCREENSAVER_ENABLED, userTracker.getUserId()) {
116             @Override
117             protected void handleValueChanged(int value, boolean observedChange) {
118                 refreshState();
119             }
120         };
121         mDreamSettingObserver = new SettingObserver(secureSettings, mHandler,
122                 Settings.Secure.SCREENSAVER_COMPONENTS, userTracker.getUserId()) {
123             @Override
124             protected void handleValueChanged(int value, boolean observedChange) {
125                 refreshState();
126             }
127         };
128         mUserTracker = userTracker;
129         mDreamSupported = dreamSupported;
130         mDreamOnlyEnabledForDockUser = dreamOnlyEnabledForDockUser;
131     }
132 
133     @Override
handleSetListening(boolean listening)134     public void handleSetListening(boolean listening) {
135         super.handleSetListening(listening);
136 
137         if (listening) {
138             final IntentFilter filter = new IntentFilter();
139             filter.addAction(Intent.ACTION_DREAMING_STARTED);
140             filter.addAction(Intent.ACTION_DREAMING_STOPPED);
141             filter.addAction(Intent.ACTION_DOCK_EVENT);
142             mBroadcastDispatcher.registerReceiver(mReceiver, filter);
143         } else {
144             mBroadcastDispatcher.unregisterReceiver(mReceiver);
145         }
146         mEnabledSettingObserver.setListening(listening);
147         mDreamSettingObserver.setListening(listening);
148     }
149 
150     @Override
newTileState()151     public BooleanState newTileState() {
152         return new BooleanState();
153     }
154 
155     @Override
handleClick(@ullable View view)156     protected void handleClick(@Nullable View view) {
157         try {
158             if (mDreamManager.isDreaming()) {
159                 mDreamManager.awaken();
160             } else {
161                 mDreamManager.dream();
162             }
163         } catch (RemoteException e) {
164             Log.e(LOG_TAG, "Can't dream", e);
165         }
166     }
167 
168     @Override
handleUpdateState(BooleanState state, Object arg)169     protected void handleUpdateState(BooleanState state, Object arg) {
170         state.label = getTileLabel();
171         state.secondaryLabel = getActiveDreamName();
172         state.contentDescription = getContentDescription(state.secondaryLabel);
173         state.icon = mIsDocked ? mIconDocked : mIconUndocked;
174 
175         if (getActiveDream() == null || !isScreensaverEnabled()) {
176             state.state = Tile.STATE_UNAVAILABLE;
177         } else {
178             state.state = isDreaming() ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
179         }
180     }
181 
182     @Nullable
183     @Override
getLongClickIntent()184     public Intent getLongClickIntent() {
185         return new Intent(Settings.ACTION_DREAM_SETTINGS);
186     }
187 
188     @Override
getTileLabel()189     public CharSequence getTileLabel() {
190         return mContext.getString(R.string.quick_settings_screensaver_label);
191     }
192 
193     @Override
isAvailable()194     public boolean isAvailable() {
195         // Only enable for devices that have dreams for the user(s) that can dream.
196         // For now, restrict to debug users.
197         return Build.isDebuggable()
198                 && mDreamSupported
199                 && (!mDreamOnlyEnabledForDockUser || mUserTracker.getUserInfo().isMain());
200     }
201 
202     @VisibleForTesting
getContentDescription(CharSequence dreamName)203     protected CharSequence getContentDescription(CharSequence dreamName) {
204         return !TextUtils.isEmpty(dreamName)
205                 ? getTileLabel() + ", " + dreamName : getTileLabel();
206     }
207 
isDreaming()208     private boolean isDreaming() {
209         try {
210             return mDreamManager.isDreaming();
211         } catch (RemoteException e) {
212             Log.e(LOG_TAG, "Can't check if dreaming", e);
213             return false;
214         }
215     }
216 
getActiveDream()217     private ComponentName getActiveDream() {
218         try {
219             final ComponentName[] dreams = mDreamManager.getDreamComponentsForUser(
220                                                 mUserTracker.getUserId());
221             return dreams != null && dreams.length > 0 ? dreams[0] : null;
222         } catch (RemoteException e) {
223             Log.w(TAG, "Failed to get active dream", e);
224             return null;
225         }
226     }
227 
getActiveDreamName()228     private CharSequence getActiveDreamName() {
229         final ComponentName componentName = getActiveDream();
230         if (componentName != null) {
231             PackageManager pm = mContext.getPackageManager();
232             try {
233                 ServiceInfo ri = pm.getServiceInfo(componentName, 0);
234                 if (ri != null) {
235                     return ri.loadLabel(pm);
236                 }
237             } catch (PackageManager.NameNotFoundException exc) {
238                 return null; // uninstalled?
239             }
240         }
241         return null;
242     }
243 
isScreensaverEnabled()244     private boolean isScreensaverEnabled() {
245         return mEnabledSettingObserver.getValue() == 1;
246     }
247 }
248