• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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