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.statusbar.IStatusBarService;
52 import com.android.internal.view.IInputMethodManager;
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     private static final String TAG = "DevelopmentTiles";
62 
isEnabled()63     protected abstract boolean isEnabled();
64 
setIsEnabled(boolean isEnabled)65     protected abstract void setIsEnabled(boolean isEnabled);
66 
67     @Override
onStartListening()68     public void onStartListening() {
69         super.onStartListening();
70         refresh();
71     }
72 
refresh()73     public void refresh() {
74         final int state;
75         if (!DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(this)) {
76             // Reset to disabled state if dev option is off.
77             if (isEnabled()) {
78                 setIsEnabled(false);
79                 SystemPropPoker.getInstance().poke();
80             }
81             final ComponentName cn = new ComponentName(getPackageName(), getClass().getName());
82             try {
83                 getPackageManager().setComponentEnabledSetting(
84                         cn, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
85                         PackageManager.DONT_KILL_APP);
86                 final IStatusBarService statusBarService = IStatusBarService.Stub.asInterface(
87                         ServiceManager.checkService(Context.STATUS_BAR_SERVICE));
88                 if (statusBarService != null) {
89                     statusBarService.remTile(cn);
90                 }
91             } catch (RemoteException e) {
92                 Log.e(TAG, "Failed to modify QS tile for component " +
93                         cn.toString(), e);
94             }
95             state = Tile.STATE_UNAVAILABLE;
96         } else {
97             state = isEnabled() ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
98         }
99         getQsTile().setState(state);
100         getQsTile().updateTile();
101     }
102 
103     @Override
onClick()104     public void onClick() {
105         setIsEnabled(getQsTile().getState() == Tile.STATE_INACTIVE);
106         SystemPropPoker.getInstance().poke(); // Settings app magic
107         refresh();
108     }
109 
110     /**
111      * Tile to control the "Show layout bounds" developer setting
112      */
113     public static class ShowLayout extends DevelopmentTiles {
114 
115         @Override
isEnabled()116         protected boolean isEnabled() {
117             return DisplayProperties.debug_layout().orElse(false);
118         }
119 
120         @Override
setIsEnabled(boolean isEnabled)121         protected void setIsEnabled(boolean isEnabled) {
122             DisplayProperties.debug_layout(isEnabled);
123         }
124     }
125 
126     /**
127      * Tile to control the "GPU profiling" developer setting
128      */
129     public static class GPUProfiling extends DevelopmentTiles {
130 
131         @Override
isEnabled()132         protected boolean isEnabled() {
133             final String value = SystemProperties.get(ThreadedRenderer.PROFILE_PROPERTY);
134             return value.equals("visual_bars");
135         }
136 
137         @Override
setIsEnabled(boolean isEnabled)138         protected void setIsEnabled(boolean isEnabled) {
139             SystemProperties.set(ThreadedRenderer.PROFILE_PROPERTY, isEnabled ? "visual_bars" : "");
140         }
141     }
142 
143     /**
144      * Tile to control the "Force RTL" developer setting
145      */
146     public static class ForceRTL extends DevelopmentTiles {
147 
148         @Override
isEnabled()149         protected boolean isEnabled() {
150             return Settings.Global.getInt(
151                     getContentResolver(), Settings.Global.DEVELOPMENT_FORCE_RTL, 0) != 0;
152         }
153 
154         @Override
setIsEnabled(boolean isEnabled)155         protected void setIsEnabled(boolean isEnabled) {
156             Settings.Global.putInt(
157                     getContentResolver(), Settings.Global.DEVELOPMENT_FORCE_RTL, isEnabled ? 1 : 0);
158             DisplayProperties.debug_force_rtl(isEnabled);
159             LocalePicker.updateLocales(getResources().getConfiguration().getLocales());
160         }
161     }
162 
163     /**
164      * Tile to control the "Animation speed" developer setting
165      */
166     public static class AnimationSpeed extends DevelopmentTiles {
167 
168         @Override
isEnabled()169         protected boolean isEnabled() {
170             IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
171             try {
172                 return wm.getAnimationScale(0) != 1;
173             } catch (RemoteException e) {
174             }
175             return false;
176         }
177 
178         @Override
setIsEnabled(boolean isEnabled)179         protected void setIsEnabled(boolean isEnabled) {
180             IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
181             float scale = isEnabled ? 10 : 1;
182             try {
183                 wm.setAnimationScale(0, scale);
184                 wm.setAnimationScale(1, scale);
185                 wm.setAnimationScale(2, scale);
186             } catch (RemoteException e) {
187             }
188         }
189     }
190 
191     /**
192      * Tile to toggle Winscope trace which consists of Window and Layer traces.
193      */
194     public static class WinscopeTrace extends DevelopmentTiles {
195         @VisibleForTesting
196         static final int SURFACE_FLINGER_LAYER_TRACE_CONTROL_CODE = 1025;
197         @VisibleForTesting
198         static final int SURFACE_FLINGER_LAYER_TRACE_STATUS_CODE = 1026;
199         private IBinder mSurfaceFlinger;
200         private IWindowManager mWindowManager;
201         private IInputMethodManager mInputMethodManager;
202         private Toast mToast;
203 
204         @Override
onCreate()205         public void onCreate() {
206             super.onCreate();
207             mWindowManager = WindowManagerGlobal.getWindowManagerService();
208             mSurfaceFlinger = ServiceManager.getService("SurfaceFlinger");
209             mInputMethodManager = IInputMethodManager.Stub.asInterface(
210                     ServiceManager.getService("input_method"));
211             Context context = getApplicationContext();
212             CharSequence text = "Trace files written to /data/misc/wmtrace";
213             mToast = Toast.makeText(context, text, Toast.LENGTH_LONG);
214         }
215 
isWindowTraceEnabled()216         private boolean isWindowTraceEnabled() {
217             try {
218                 return mWindowManager.isWindowTraceEnabled();
219             } catch (RemoteException e) {
220                 Log.e(TAG,
221                         "Could not get window trace status, defaulting to false." + e.toString());
222             }
223             return false;
224         }
225 
isLayerTraceEnabled()226         private boolean isLayerTraceEnabled() {
227             boolean layerTraceEnabled = false;
228             Parcel reply = null;
229             Parcel data = null;
230             try {
231                 if (mSurfaceFlinger != null) {
232                     reply = Parcel.obtain();
233                     data = Parcel.obtain();
234                     data.writeInterfaceToken("android.ui.ISurfaceComposer");
235                     mSurfaceFlinger.transact(SURFACE_FLINGER_LAYER_TRACE_STATUS_CODE,
236                             data, reply, 0 /* flags */);
237                     layerTraceEnabled = reply.readBoolean();
238                 }
239             } catch (RemoteException e) {
240                 Log.e(TAG, "Could not get layer trace status, defaulting to false." + e.toString());
241             } finally {
242                 if (data != null) {
243                     data.recycle();
244                     reply.recycle();
245                 }
246             }
247             return layerTraceEnabled;
248         }
249 
isSystemUiTracingEnabled()250         private boolean isSystemUiTracingEnabled() {
251             try {
252                 final IStatusBarService statusBarService = IStatusBarService.Stub.asInterface(
253                         ServiceManager.checkService(Context.STATUS_BAR_SERVICE));
254                 if (statusBarService != null) {
255                     return statusBarService.isTracing();
256                 }
257             } catch (RemoteException e) {
258                 Log.e(TAG, "Could not get system ui tracing status." + e.toString());
259             }
260             return false;
261         }
262 
isImeTraceEnabled()263         private boolean isImeTraceEnabled() {
264             try {
265                 return mInputMethodManager.isImeTraceEnabled();
266             } catch (RemoteException e) {
267                 Log.e(TAG, "Could not get ime trace status, defaulting to false.", e);
268             }
269             return false;
270         }
271 
272         @Override
isEnabled()273         protected boolean isEnabled() {
274             return isWindowTraceEnabled() || isLayerTraceEnabled() || isSystemUiTracingEnabled()
275                     || isImeTraceEnabled();
276         }
277 
setWindowTraceEnabled(boolean isEnabled)278         private void setWindowTraceEnabled(boolean isEnabled) {
279             try {
280                 if (isEnabled) {
281                     mWindowManager.startWindowTrace();
282                 } else {
283                     mWindowManager.stopWindowTrace();
284                 }
285             } catch (RemoteException e) {
286                 Log.e(TAG, "Could not set window trace status." + e.toString());
287             }
288         }
289 
setLayerTraceEnabled(boolean isEnabled)290         private void setLayerTraceEnabled(boolean isEnabled) {
291             Parcel data = null;
292             try {
293                 if (mSurfaceFlinger != null) {
294                     data = Parcel.obtain();
295                     data.writeInterfaceToken("android.ui.ISurfaceComposer");
296                     data.writeInt(isEnabled ? 1 : 0);
297                     mSurfaceFlinger.transact(SURFACE_FLINGER_LAYER_TRACE_CONTROL_CODE,
298                             data, null, 0 /* flags */);
299                 }
300             } catch (RemoteException e) {
301                 Log.e(TAG, "Could not set layer tracing." + e.toString());
302             } finally {
303                 if (data != null) {
304                     data.recycle();
305                 }
306             }
307         }
308 
setSystemUiTracing(boolean isEnabled)309         private void setSystemUiTracing(boolean isEnabled) {
310             try {
311                 final IStatusBarService statusBarService = IStatusBarService.Stub.asInterface(
312                         ServiceManager.checkService(Context.STATUS_BAR_SERVICE));
313                 if (statusBarService != null) {
314                     if (isEnabled) {
315                         statusBarService.startTracing();
316                     } else {
317                         statusBarService.stopTracing();
318                     }
319                 }
320             } catch (RemoteException e) {
321                 Log.e(TAG, "Could not set system ui tracing." + e.toString());
322             }
323         }
324 
setImeTraceEnabled(boolean isEnabled)325         private void setImeTraceEnabled(boolean isEnabled) {
326             try {
327                 if (isEnabled) {
328                     mInputMethodManager.startImeTrace();
329                 } else {
330                     mInputMethodManager.stopImeTrace();
331                 }
332             } catch (RemoteException e) {
333                 Log.e(TAG, "Could not set ime trace status." + e.toString());
334             }
335         }
336 
337         @Override
setIsEnabled(boolean isEnabled)338         protected void setIsEnabled(boolean isEnabled) {
339             setWindowTraceEnabled(isEnabled);
340             setLayerTraceEnabled(isEnabled);
341             setSystemUiTracing(isEnabled);
342             setImeTraceEnabled(isEnabled);
343             if (!isEnabled) {
344                 mToast.show();
345             }
346         }
347     }
348 
349     /**
350      * Tile to toggle sensors off to control camera, mic, and sensors managed by the SensorManager.
351      */
352     public static class SensorsOff extends DevelopmentTiles {
353         private Context mContext;
354         private SensorPrivacyManager mSensorPrivacyManager;
355         private KeyguardManager mKeyguardManager;
356         private MetricsFeatureProvider mMetricsFeatureProvider;
357         private boolean mIsEnabled;
358 
359         @Override
onCreate()360         public void onCreate() {
361             super.onCreate();
362             mContext = getApplicationContext();
363             mSensorPrivacyManager = (SensorPrivacyManager) mContext.getSystemService(
364                     Context.SENSOR_PRIVACY_SERVICE);
365             mIsEnabled = mSensorPrivacyManager.isAllSensorPrivacyEnabled();
366             mMetricsFeatureProvider = FeatureFactory.getFactory(
367                     mContext).getMetricsFeatureProvider();
368             mKeyguardManager = (KeyguardManager) mContext.getSystemService(
369                     Context.KEYGUARD_SERVICE);
370         }
371 
372         @Override
isEnabled()373         protected boolean isEnabled() {
374             return mIsEnabled;
375         }
376 
377         @Override
setIsEnabled(boolean isEnabled)378         public void setIsEnabled(boolean isEnabled) {
379             // Don't allow sensors to be reenabled from the lock screen.
380             if (mIsEnabled && mKeyguardManager.isKeyguardLocked()) {
381                 return;
382             }
383             mMetricsFeatureProvider.action(getApplicationContext(), SettingsEnums.QS_SENSOR_PRIVACY,
384                     isEnabled);
385             mIsEnabled = isEnabled;
386             mSensorPrivacyManager.setAllSensorPrivacy(isEnabled);
387         }
388     }
389 
390     /**
391      * Tile to control the "Wireless debugging" developer setting
392      */
393     public static class WirelessDebugging extends DevelopmentTiles {
394         private Context mContext;
395         private KeyguardManager mKeyguardManager;
396         private Toast mToast;
397         private final Handler mHandler = new Handler(Looper.getMainLooper());
398         private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
399             @Override
400             public void onChange(boolean selfChange, Uri uri) {
401                 refresh();
402             }
403         };
404 
405         @Override
onCreate()406         public void onCreate() {
407             super.onCreate();
408             mContext = getApplicationContext();
409             mKeyguardManager = (KeyguardManager) mContext.getSystemService(
410                     Context.KEYGUARD_SERVICE);
411             mToast = Toast.makeText(mContext, R.string.adb_wireless_no_network_msg,
412                     Toast.LENGTH_LONG);
413         }
414 
415         @Override
onStartListening()416         public void onStartListening() {
417             super.onStartListening();
418             getContentResolver().registerContentObserver(
419                     Settings.Global.getUriFor(Settings.Global.ADB_WIFI_ENABLED), false,
420                     mSettingsObserver);
421         }
422 
423         @Override
onStopListening()424         public void onStopListening() {
425             super.onStopListening();
426             getContentResolver().unregisterContentObserver(mSettingsObserver);
427         }
428 
429         @Override
isEnabled()430         protected boolean isEnabled() {
431             return isAdbWifiEnabled();
432         }
433 
434         @Override
setIsEnabled(boolean isEnabled)435         public void setIsEnabled(boolean isEnabled) {
436             // Don't allow Wireless Debugging to be enabled from the lock screen.
437             if (isEnabled && mKeyguardManager.isKeyguardLocked()) {
438                 return;
439             }
440 
441             // Show error toast if not connected to Wi-Fi
442             if (isEnabled && !WirelessDebuggingPreferenceController.isWifiConnected(mContext)) {
443                 // Close quick shade
444                 sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
445                 mToast.show();
446                 return;
447             }
448 
449             writeAdbWifiSetting(isEnabled);
450         }
451 
isAdbWifiEnabled()452         private boolean isAdbWifiEnabled() {
453             return Settings.Global.getInt(getContentResolver(), Settings.Global.ADB_WIFI_ENABLED,
454                     ADB_SETTING_OFF) != ADB_SETTING_OFF;
455         }
456 
writeAdbWifiSetting(boolean enabled)457         protected void writeAdbWifiSetting(boolean enabled) {
458             Settings.Global.putInt(getContentResolver(), Settings.Global.ADB_WIFI_ENABLED,
459                     enabled ? ADB_SETTING_ON : ADB_SETTING_OFF);
460         }
461     }
462 
463     /**
464      * Tile to control the "Show taps" developer setting
465      */
466     public static class ShowTaps extends DevelopmentTiles {
467         private static final int SETTING_VALUE_ON = 1;
468         private static final int SETTING_VALUE_OFF = 0;
469         private Context mContext;
470 
471         @Override
onCreate()472         public void onCreate() {
473             super.onCreate();
474             mContext = getApplicationContext();
475         }
476 
477         @Override
isEnabled()478         protected boolean isEnabled() {
479             return Settings.System.getInt(mContext.getContentResolver(),
480                 Settings.System.SHOW_TOUCHES, SETTING_VALUE_OFF) == SETTING_VALUE_ON;
481         }
482 
483         @Override
setIsEnabled(boolean isEnabled)484         protected void setIsEnabled(boolean isEnabled) {
485             Settings.System.putInt(mContext.getContentResolver(),
486                 Settings.System.SHOW_TOUCHES, isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF);
487         }
488     }
489 }
490