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