1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.android.settings.development.qstile; 18 19 import static com.android.settings.development.AdbPreferenceController.ADB_SETTING_OFF; 20 import static com.android.settings.development.AdbPreferenceController.ADB_SETTING_ON; 21 22 import android.app.KeyguardManager; 23 import android.app.settings.SettingsEnums; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.pm.PackageManager; 28 import android.database.ContentObserver; 29 import android.hardware.SensorPrivacyManager; 30 import android.net.Uri; 31 import android.os.Handler; 32 import android.os.IBinder; 33 import android.os.Looper; 34 import android.os.Parcel; 35 import android.os.RemoteException; 36 import android.os.ServiceManager; 37 import android.os.SystemProperties; 38 import android.provider.Settings; 39 import android.service.quicksettings.Tile; 40 import android.service.quicksettings.TileService; 41 import android.sysprop.DisplayProperties; 42 import android.util.Log; 43 import android.view.IWindowManager; 44 import android.view.ThreadedRenderer; 45 import android.view.WindowManagerGlobal; 46 import android.widget.Toast; 47 48 import androidx.annotation.VisibleForTesting; 49 50 import com.android.internal.app.LocalePicker; 51 import com.android.internal.inputmethod.ImeTracing; 52 import com.android.internal.statusbar.IStatusBarService; 53 import com.android.settings.R; 54 import com.android.settings.development.WirelessDebuggingPreferenceController; 55 import com.android.settings.overlay.FeatureFactory; 56 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 57 import com.android.settingslib.development.DevelopmentSettingsEnabler; 58 import com.android.settingslib.development.SystemPropPoker; 59 60 public abstract class DevelopmentTiles extends TileService { 61 62 /** 63 * Meta-data for a development tile to declare a sysprop flag that needs to be enabled for 64 * the tile to be available. 65 * 66 * To define the flag, set this meta-data on the tile's manifest declaration. 67 * <pre class="prettyprint"> 68 * {@literal 69 * <meta-data android:name="com.android.settings.development.qstile.REQUIRES_SYSTEM_PROPERTY" 70 * android:value="persist.debug.flag_name_here" /> 71 * } 72 * </pre> 73 */ 74 public static final String META_DATA_REQUIRES_SYSTEM_PROPERTY = 75 "com.android.settings.development.qstile.REQUIRES_SYSTEM_PROPERTY"; 76 77 private static final String TAG = "DevelopmentTiles"; 78 isEnabled()79 protected abstract boolean isEnabled(); 80 setIsEnabled(boolean isEnabled)81 protected abstract void setIsEnabled(boolean isEnabled); 82 83 @Override onStartListening()84 public void onStartListening() { 85 super.onStartListening(); 86 refresh(); 87 } 88 refresh()89 public void refresh() { 90 final int state; 91 if (!DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(this)) { 92 // Reset to disabled state if dev option is off. 93 if (isEnabled()) { 94 setIsEnabled(false); 95 SystemPropPoker.getInstance().poke(); 96 } 97 final ComponentName cn = new ComponentName(getPackageName(), getClass().getName()); 98 try { 99 getPackageManager().setComponentEnabledSetting( 100 cn, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 101 PackageManager.DONT_KILL_APP); 102 final IStatusBarService statusBarService = IStatusBarService.Stub.asInterface( 103 ServiceManager.checkService(Context.STATUS_BAR_SERVICE)); 104 if (statusBarService != null) { 105 statusBarService.remTile(cn); 106 } 107 } catch (RemoteException e) { 108 Log.e(TAG, "Failed to modify QS tile for component " + 109 cn.toString(), e); 110 } 111 state = Tile.STATE_UNAVAILABLE; 112 } else { 113 state = isEnabled() ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; 114 } 115 getQsTile().setState(state); 116 getQsTile().updateTile(); 117 } 118 119 @Override onClick()120 public void onClick() { 121 setIsEnabled(getQsTile().getState() == Tile.STATE_INACTIVE); 122 SystemPropPoker.getInstance().poke(); // Settings app magic 123 refresh(); 124 } 125 126 /** 127 * Tile to control the "Show layout bounds" developer setting 128 */ 129 public static class ShowLayout extends DevelopmentTiles { 130 131 @Override isEnabled()132 protected boolean isEnabled() { 133 return DisplayProperties.debug_layout().orElse(false); 134 } 135 136 @Override setIsEnabled(boolean isEnabled)137 protected void setIsEnabled(boolean isEnabled) { 138 DisplayProperties.debug_layout(isEnabled); 139 } 140 } 141 142 /** 143 * Tile to control the "GPU profiling" developer setting 144 */ 145 public static class GPUProfiling extends DevelopmentTiles { 146 147 @Override isEnabled()148 protected boolean isEnabled() { 149 final String value = SystemProperties.get(ThreadedRenderer.PROFILE_PROPERTY); 150 return value.equals("visual_bars"); 151 } 152 153 @Override setIsEnabled(boolean isEnabled)154 protected void setIsEnabled(boolean isEnabled) { 155 SystemProperties.set(ThreadedRenderer.PROFILE_PROPERTY, isEnabled ? "visual_bars" : ""); 156 } 157 } 158 159 /** 160 * Tile to control the "Force RTL" developer setting 161 */ 162 public static class ForceRTL extends DevelopmentTiles { 163 164 @Override isEnabled()165 protected boolean isEnabled() { 166 return Settings.Global.getInt( 167 getContentResolver(), Settings.Global.DEVELOPMENT_FORCE_RTL, 0) != 0; 168 } 169 170 @Override setIsEnabled(boolean isEnabled)171 protected void setIsEnabled(boolean isEnabled) { 172 Settings.Global.putInt( 173 getContentResolver(), Settings.Global.DEVELOPMENT_FORCE_RTL, isEnabled ? 1 : 0); 174 DisplayProperties.debug_force_rtl(isEnabled); 175 LocalePicker.updateLocales(getResources().getConfiguration().getLocales()); 176 } 177 } 178 179 /** 180 * Tile to control the "Animation speed" developer setting 181 */ 182 public static class AnimationSpeed extends DevelopmentTiles { 183 184 @Override isEnabled()185 protected boolean isEnabled() { 186 IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); 187 try { 188 return wm.getAnimationScale(0) != 1; 189 } catch (RemoteException e) { 190 } 191 return false; 192 } 193 194 @Override setIsEnabled(boolean isEnabled)195 protected void setIsEnabled(boolean isEnabled) { 196 IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); 197 float scale = isEnabled ? 10 : 1; 198 try { 199 wm.setAnimationScale(0, scale); 200 wm.setAnimationScale(1, scale); 201 wm.setAnimationScale(2, scale); 202 } catch (RemoteException e) { 203 } 204 } 205 } 206 207 /** 208 * Tile to toggle Winscope trace which consists of Window and Layer traces. 209 */ 210 public static class WinscopeTrace extends DevelopmentTiles { 211 @VisibleForTesting 212 static final int SURFACE_FLINGER_LAYER_TRACE_CONTROL_CODE = 1025; 213 @VisibleForTesting 214 static final int SURFACE_FLINGER_LAYER_TRACE_STATUS_CODE = 1026; 215 private static final String VIEW_CAPTURE_ENABLED = "view_capture_enabled"; 216 private IBinder mSurfaceFlinger; 217 private IWindowManager mWindowManager; 218 private ImeTracing mImeTracing; 219 private Toast mToast; 220 221 @Override onCreate()222 public void onCreate() { 223 super.onCreate(); 224 mWindowManager = WindowManagerGlobal.getWindowManagerService(); 225 mSurfaceFlinger = ServiceManager.getService("SurfaceFlinger"); 226 mImeTracing = ImeTracing.getInstance(); 227 Context context = getApplicationContext(); 228 CharSequence text = "Trace files written to /data/misc/wmtrace"; 229 mToast = Toast.makeText(context, text, Toast.LENGTH_LONG); 230 } 231 isWindowTraceEnabled()232 private boolean isWindowTraceEnabled() { 233 try { 234 return mWindowManager.isWindowTraceEnabled(); 235 } catch (RemoteException e) { 236 Log.e(TAG, 237 "Could not get window trace status, defaulting to false." + e.toString()); 238 } 239 return false; 240 } 241 isLayerTraceEnabled()242 private boolean isLayerTraceEnabled() { 243 boolean layerTraceEnabled = false; 244 Parcel reply = null; 245 Parcel data = null; 246 try { 247 if (mSurfaceFlinger != null) { 248 reply = Parcel.obtain(); 249 data = Parcel.obtain(); 250 data.writeInterfaceToken("android.ui.ISurfaceComposer"); 251 mSurfaceFlinger.transact(SURFACE_FLINGER_LAYER_TRACE_STATUS_CODE, 252 data, reply, 0 /* flags */); 253 layerTraceEnabled = reply.readBoolean(); 254 } 255 } catch (RemoteException e) { 256 Log.e(TAG, "Could not get layer trace status, defaulting to false." + e.toString()); 257 } finally { 258 if (data != null) { 259 data.recycle(); 260 reply.recycle(); 261 } 262 } 263 return layerTraceEnabled; 264 } 265 isSystemUiTracingEnabled()266 private boolean isSystemUiTracingEnabled() { 267 try { 268 final IStatusBarService statusBarService = IStatusBarService.Stub.asInterface( 269 ServiceManager.checkService(Context.STATUS_BAR_SERVICE)); 270 if (statusBarService != null) { 271 return statusBarService.isTracing(); 272 } 273 } catch (RemoteException e) { 274 Log.e(TAG, "Could not get system ui tracing status." + e.toString()); 275 } 276 return false; 277 } 278 isImeTraceEnabled()279 private boolean isImeTraceEnabled() { 280 return mImeTracing.isEnabled(); 281 } 282 isViewCaptureEnabled()283 private boolean isViewCaptureEnabled() { 284 // Add null checking to avoid test case failure. 285 if (getApplicationContext() != null) { 286 return Settings.Global.getInt(getApplicationContext().getContentResolver(), 287 VIEW_CAPTURE_ENABLED, 0) != 0; 288 } 289 return false; 290 } 291 292 @Override isEnabled()293 protected boolean isEnabled() { 294 return isWindowTraceEnabled() || isLayerTraceEnabled() || isSystemUiTracingEnabled() 295 || isImeTraceEnabled() || isViewCaptureEnabled(); 296 } 297 setWindowTraceEnabled(boolean isEnabled)298 private void setWindowTraceEnabled(boolean isEnabled) { 299 try { 300 if (isEnabled) { 301 mWindowManager.startWindowTrace(); 302 } else { 303 mWindowManager.stopWindowTrace(); 304 } 305 } catch (RemoteException e) { 306 Log.e(TAG, "Could not set window trace status." + e.toString()); 307 } 308 } 309 setLayerTraceEnabled(boolean isEnabled)310 private void setLayerTraceEnabled(boolean isEnabled) { 311 Parcel data = null; 312 try { 313 if (mSurfaceFlinger != null) { 314 data = Parcel.obtain(); 315 data.writeInterfaceToken("android.ui.ISurfaceComposer"); 316 data.writeInt(isEnabled ? 1 : 0); 317 mSurfaceFlinger.transact(SURFACE_FLINGER_LAYER_TRACE_CONTROL_CODE, 318 data, null, 0 /* flags */); 319 } 320 } catch (RemoteException e) { 321 Log.e(TAG, "Could not set layer tracing." + e.toString()); 322 } finally { 323 if (data != null) { 324 data.recycle(); 325 } 326 } 327 } 328 setSystemUiTracing(boolean isEnabled)329 private void setSystemUiTracing(boolean isEnabled) { 330 try { 331 final IStatusBarService statusBarService = IStatusBarService.Stub.asInterface( 332 ServiceManager.checkService(Context.STATUS_BAR_SERVICE)); 333 if (statusBarService != null) { 334 if (isEnabled) { 335 statusBarService.startTracing(); 336 } else { 337 statusBarService.stopTracing(); 338 } 339 } 340 } catch (RemoteException e) { 341 Log.e(TAG, "Could not set system ui tracing." + e.toString()); 342 } 343 } 344 setImeTraceEnabled(boolean isEnabled)345 private void setImeTraceEnabled(boolean isEnabled) { 346 if (isEnabled) { 347 mImeTracing.startImeTrace(); 348 } else { 349 mImeTracing.stopImeTrace(); 350 } 351 } 352 setViewCaptureEnabled(boolean isEnabled)353 private void setViewCaptureEnabled(boolean isEnabled) { 354 // Add null checking to avoid test case failure. 355 if (getApplicationContext() != null) { 356 Settings.Global.putInt(getApplicationContext() 357 .getContentResolver(), VIEW_CAPTURE_ENABLED, isEnabled ? 1 : 0); 358 } 359 } 360 361 @Override setIsEnabled(boolean isEnabled)362 protected void setIsEnabled(boolean isEnabled) { 363 setWindowTraceEnabled(isEnabled); 364 setLayerTraceEnabled(isEnabled); 365 setSystemUiTracing(isEnabled); 366 setImeTraceEnabled(isEnabled); 367 setViewCaptureEnabled(isEnabled); 368 if (!isEnabled) { 369 mToast.show(); 370 } 371 } 372 } 373 374 /** 375 * Tile to toggle sensors off to control camera, mic, and sensors managed by the SensorManager. 376 */ 377 public static class SensorsOff extends DevelopmentTiles { 378 private Context mContext; 379 private SensorPrivacyManager mSensorPrivacyManager; 380 private KeyguardManager mKeyguardManager; 381 private MetricsFeatureProvider mMetricsFeatureProvider; 382 private boolean mIsEnabled; 383 384 @Override onCreate()385 public void onCreate() { 386 super.onCreate(); 387 mContext = getApplicationContext(); 388 mSensorPrivacyManager = (SensorPrivacyManager) mContext.getSystemService( 389 Context.SENSOR_PRIVACY_SERVICE); 390 mIsEnabled = mSensorPrivacyManager.isAllSensorPrivacyEnabled(); 391 mMetricsFeatureProvider = FeatureFactory.getFactory( 392 mContext).getMetricsFeatureProvider(); 393 mKeyguardManager = (KeyguardManager) mContext.getSystemService( 394 Context.KEYGUARD_SERVICE); 395 } 396 397 @Override isEnabled()398 protected boolean isEnabled() { 399 return mIsEnabled; 400 } 401 402 @Override setIsEnabled(boolean isEnabled)403 public void setIsEnabled(boolean isEnabled) { 404 // Don't allow sensors to be reenabled from the lock screen. 405 if (mIsEnabled && mKeyguardManager.isKeyguardLocked()) { 406 return; 407 } 408 mMetricsFeatureProvider.action(getApplicationContext(), SettingsEnums.QS_SENSOR_PRIVACY, 409 isEnabled); 410 mIsEnabled = isEnabled; 411 mSensorPrivacyManager.setAllSensorPrivacy(isEnabled); 412 } 413 } 414 415 /** 416 * Tile to control the "Wireless debugging" developer setting 417 */ 418 public static class WirelessDebugging extends DevelopmentTiles { 419 private Context mContext; 420 private KeyguardManager mKeyguardManager; 421 private Toast mToast; 422 private final Handler mHandler = new Handler(Looper.getMainLooper()); 423 private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { 424 @Override 425 public void onChange(boolean selfChange, Uri uri) { 426 refresh(); 427 } 428 }; 429 430 @Override onCreate()431 public void onCreate() { 432 super.onCreate(); 433 mContext = getApplicationContext(); 434 mKeyguardManager = (KeyguardManager) mContext.getSystemService( 435 Context.KEYGUARD_SERVICE); 436 mToast = Toast.makeText(mContext, R.string.adb_wireless_no_network_msg, 437 Toast.LENGTH_LONG); 438 } 439 440 @Override onStartListening()441 public void onStartListening() { 442 super.onStartListening(); 443 getContentResolver().registerContentObserver( 444 Settings.Global.getUriFor(Settings.Global.ADB_WIFI_ENABLED), false, 445 mSettingsObserver); 446 } 447 448 @Override onStopListening()449 public void onStopListening() { 450 super.onStopListening(); 451 getContentResolver().unregisterContentObserver(mSettingsObserver); 452 } 453 454 @Override isEnabled()455 protected boolean isEnabled() { 456 return isAdbWifiEnabled(); 457 } 458 459 @Override setIsEnabled(boolean isEnabled)460 public void setIsEnabled(boolean isEnabled) { 461 // Don't allow Wireless Debugging to be enabled from the lock screen. 462 if (isEnabled && mKeyguardManager.isKeyguardLocked()) { 463 return; 464 } 465 466 // Show error toast if not connected to Wi-Fi 467 if (isEnabled && !WirelessDebuggingPreferenceController.isWifiConnected(mContext)) { 468 // Close quick shade 469 sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); 470 mToast.show(); 471 return; 472 } 473 474 writeAdbWifiSetting(isEnabled); 475 } 476 isAdbWifiEnabled()477 private boolean isAdbWifiEnabled() { 478 return Settings.Global.getInt(getContentResolver(), Settings.Global.ADB_WIFI_ENABLED, 479 ADB_SETTING_OFF) != ADB_SETTING_OFF; 480 } 481 writeAdbWifiSetting(boolean enabled)482 protected void writeAdbWifiSetting(boolean enabled) { 483 Settings.Global.putInt(getContentResolver(), Settings.Global.ADB_WIFI_ENABLED, 484 enabled ? ADB_SETTING_ON : ADB_SETTING_OFF); 485 } 486 } 487 488 /** 489 * Tile to control the "Show taps" developer setting 490 */ 491 public static class ShowTaps extends DevelopmentTiles { 492 private static final int SETTING_VALUE_ON = 1; 493 private static final int SETTING_VALUE_OFF = 0; 494 private Context mContext; 495 496 @Override onCreate()497 public void onCreate() { 498 super.onCreate(); 499 mContext = getApplicationContext(); 500 } 501 502 @Override isEnabled()503 protected boolean isEnabled() { 504 return Settings.System.getInt(mContext.getContentResolver(), 505 Settings.System.SHOW_TOUCHES, SETTING_VALUE_OFF) == SETTING_VALUE_ON; 506 } 507 508 @Override setIsEnabled(boolean isEnabled)509 protected void setIsEnabled(boolean isEnabled) { 510 Settings.System.putInt(mContext.getContentResolver(), 511 Settings.System.SHOW_TOUCHES, isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF); 512 } 513 } 514 515 /** 516 * Tile to enable desktop mode 517 */ 518 public static class DesktopMode extends DevelopmentTiles { 519 520 private static final int SETTING_VALUE_ON = 1; 521 private static final int SETTING_VALUE_OFF = 0; 522 private Context mContext; 523 524 @Override onCreate()525 public void onCreate() { 526 super.onCreate(); 527 mContext = getApplicationContext(); 528 } 529 530 @Override isEnabled()531 protected boolean isEnabled() { 532 return Settings.System.getInt(mContext.getContentResolver(), 533 Settings.System.DESKTOP_MODE, SETTING_VALUE_OFF) == SETTING_VALUE_ON; 534 } 535 isDesktopModeFlagEnabled()536 private boolean isDesktopModeFlagEnabled() { 537 return SystemProperties.getBoolean("persist.wm.debug.desktop_mode", false); 538 } 539 isFreeformFlagEnabled()540 private boolean isFreeformFlagEnabled() { 541 return Settings.Global.getInt(mContext.getContentResolver(), 542 Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, SETTING_VALUE_OFF) 543 == SETTING_VALUE_ON; 544 } 545 isCaptionOnShellEnabled()546 private boolean isCaptionOnShellEnabled() { 547 return SystemProperties.getBoolean("persist.wm.debug.caption_on_shell", false); 548 } 549 550 @Override setIsEnabled(boolean isEnabled)551 protected void setIsEnabled(boolean isEnabled) { 552 if (isEnabled) { 553 // Check that all required features are enabled 554 if (!isDesktopModeFlagEnabled()) { 555 closeShade(); 556 showMessage( 557 "Enable 'Desktop Windowing Proto 1' from the Flag Flipper app"); 558 return; 559 } 560 if (!isCaptionOnShellEnabled()) { 561 closeShade(); 562 showMessage("Enable 'Captions in Shell' from the Flag Flipper app"); 563 return; 564 } 565 if (!isFreeformFlagEnabled()) { 566 closeShade(); 567 showMessage( 568 "Enable freeform windows from developer settings"); 569 return; 570 } 571 } 572 573 Settings.System.putInt(mContext.getContentResolver(), 574 Settings.System.DESKTOP_MODE, 575 isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF); 576 closeShade(); 577 } 578 closeShade()579 private void closeShade() { 580 sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); 581 } 582 showMessage(String message)583 private void showMessage(String message) { 584 Toast.makeText(mContext, message, Toast.LENGTH_LONG).show(); 585 } 586 } 587 } 588