1 /*
2  * Copyright (C) 2017 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.settingslib.development;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.os.Bundle;
24 import android.os.SystemProperties;
25 import android.text.TextUtils;
26 
27 import androidx.annotation.VisibleForTesting;
28 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
29 import androidx.preference.ListPreference;
30 import androidx.preference.Preference;
31 import androidx.preference.PreferenceScreen;
32 
33 import com.android.settingslib.R;
34 import com.android.settingslib.core.ConfirmationDialogController;
35 import com.android.settingslib.core.lifecycle.Lifecycle;
36 import com.android.settingslib.core.lifecycle.LifecycleObserver;
37 import com.android.settingslib.core.lifecycle.events.OnCreate;
38 import com.android.settingslib.core.lifecycle.events.OnDestroy;
39 
40 public abstract class AbstractLogpersistPreferenceController extends
41         DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener,
42         LifecycleObserver, OnCreate, OnDestroy, ConfirmationDialogController {
43 
44     private static final String SELECT_LOGPERSIST_KEY = "select_logpersist";
45     private static final String SELECT_LOGPERSIST_PROPERTY = "persist.logd.logpersistd";
46     @VisibleForTesting
47     static final String ACTUAL_LOGPERSIST_PROPERTY = "logd.logpersistd";
48     @VisibleForTesting
49     static final String SELECT_LOGPERSIST_PROPERTY_SERVICE = "logcatd";
50     private static final String SELECT_LOGPERSIST_PROPERTY_CLEAR = "clear";
51     private static final String SELECT_LOGPERSIST_PROPERTY_STOP = "stop";
52     private static final String SELECT_LOGPERSIST_PROPERTY_BUFFER =
53             "persist.logd.logpersistd.buffer";
54     @VisibleForTesting
55     static final String ACTUAL_LOGPERSIST_PROPERTY_BUFFER = "logd.logpersistd.buffer";
56     private static final String ACTUAL_LOGPERSIST_PROPERTY_ENABLE = "logd.logpersistd.enable";
57 
58     private ListPreference mLogpersist;
59     private boolean mLogpersistCleared;
60 
61     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
62         @Override
63         public void onReceive(Context context, Intent intent) {
64             final String currentValue = intent.getStringExtra(
65                     AbstractLogdSizePreferenceController.EXTRA_CURRENT_LOGD_VALUE);
66             onLogdSizeSettingUpdate(currentValue);
67         }
68     };
69 
AbstractLogpersistPreferenceController(Context context, Lifecycle lifecycle)70     public AbstractLogpersistPreferenceController(Context context, Lifecycle lifecycle) {
71         super(context);
72         if (isAvailable() && lifecycle != null) {
73             lifecycle.addObserver(this);
74         }
75     }
76 
77     @Override
isAvailable()78     public boolean isAvailable() {
79         return TextUtils.equals(SystemProperties.get("ro.debuggable", "0"), "1");
80     }
81 
82     @Override
getPreferenceKey()83     public String getPreferenceKey() {
84         return SELECT_LOGPERSIST_KEY;
85     }
86 
87     @Override
displayPreference(PreferenceScreen screen)88     public void displayPreference(PreferenceScreen screen) {
89         super.displayPreference(screen);
90         if (isAvailable()) {
91             mLogpersist = (ListPreference) screen.findPreference(SELECT_LOGPERSIST_KEY);
92         }
93     }
94 
95     @Override
onPreferenceChange(Preference preference, Object newValue)96     public boolean onPreferenceChange(Preference preference, Object newValue) {
97         if (preference == mLogpersist) {
98             writeLogpersistOption(newValue, false);
99             return true;
100         } else {
101             return false;
102         }
103     }
104 
105     @Override
onCreate(Bundle savedInstanceState)106     public void onCreate(Bundle savedInstanceState) {
107         LocalBroadcastManager.getInstance(mContext).registerReceiver(mReceiver,
108                 new IntentFilter(AbstractLogdSizePreferenceController.ACTION_LOGD_SIZE_UPDATED));
109     }
110 
111     @Override
onDestroy()112     public void onDestroy() {
113         LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mReceiver);
114     }
115 
enablePreference(boolean enabled)116     public void enablePreference(boolean enabled) {
117         if (isAvailable()) {
118             mLogpersist.setEnabled(enabled);
119         }
120     }
121 
onLogdSizeSettingUpdate(String currentValue)122     private void onLogdSizeSettingUpdate(String currentValue) {
123         if (mLogpersist != null) {
124             String currentLogpersistEnable
125                     = SystemProperties.get(ACTUAL_LOGPERSIST_PROPERTY_ENABLE);
126             if ((currentLogpersistEnable == null)
127                     || !currentLogpersistEnable.equals("true")
128                     || currentValue.equals(
129                     AbstractLogdSizePreferenceController.SELECT_LOGD_OFF_SIZE_MARKER_VALUE)) {
130                 writeLogpersistOption(null, true);
131                 mLogpersist.setEnabled(false);
132             } else if (DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)) {
133                 mLogpersist.setEnabled(true);
134             }
135         }
136     }
137 
updateLogpersistValues()138     public void updateLogpersistValues() {
139         if (mLogpersist == null) {
140             return;
141         }
142         String currentValue = SystemProperties.get(ACTUAL_LOGPERSIST_PROPERTY);
143         if (currentValue == null) {
144             currentValue = "";
145         }
146         String currentBuffers = SystemProperties.get(ACTUAL_LOGPERSIST_PROPERTY_BUFFER);
147         if ((currentBuffers == null) || (currentBuffers.length() == 0)) {
148             currentBuffers = "all";
149         }
150         int index = 0;
151         if (currentValue.equals(SELECT_LOGPERSIST_PROPERTY_SERVICE)) {
152             index = 1;
153             if (currentBuffers.equals("kernel")) {
154                 index = 3;
155             } else if (!currentBuffers.equals("all") &&
156                     !currentBuffers.contains("radio") &&
157                     currentBuffers.contains("security") &&
158                     currentBuffers.contains("kernel")) {
159                 index = 2;
160                 if (!currentBuffers.contains("default")) {
161                     String[] contains = {"main", "events", "system", "crash"};
162                     for (String type : contains) {
163                         if (!currentBuffers.contains(type)) {
164                             index = 1;
165                             break;
166                         }
167                     }
168                 }
169             }
170         }
171         mLogpersist.setValue(
172                 mContext.getResources().getStringArray(R.array.select_logpersist_values)[index]);
173         mLogpersist.setSummary(
174                 mContext.getResources().getStringArray(R.array.select_logpersist_summaries)[index]);
175         if (index != 0) {
176             mLogpersistCleared = false;
177         } else if (!mLogpersistCleared) {
178             // would File.delete() directly but need to switch uid/gid to access
179             SystemProperties.set(ACTUAL_LOGPERSIST_PROPERTY, SELECT_LOGPERSIST_PROPERTY_CLEAR);
180             SystemPropPoker.getInstance().poke();
181             mLogpersistCleared = true;
182         }
183     }
184 
setLogpersistOff(boolean update)185     protected void setLogpersistOff(boolean update) {
186         SystemProperties.set(SELECT_LOGPERSIST_PROPERTY_BUFFER, "");
187         // deal with trampoline of empty properties
188         SystemProperties.set(ACTUAL_LOGPERSIST_PROPERTY_BUFFER, "");
189         SystemProperties.set(SELECT_LOGPERSIST_PROPERTY, "");
190         SystemProperties.set(ACTUAL_LOGPERSIST_PROPERTY,
191                 update ? "" : SELECT_LOGPERSIST_PROPERTY_STOP);
192         SystemPropPoker.getInstance().poke();
193         if (update) {
194             updateLogpersistValues();
195         } else {
196             for (int i = 0; i < 3; i++) {
197                 String currentValue = SystemProperties.get(ACTUAL_LOGPERSIST_PROPERTY);
198                 if ((currentValue == null) || currentValue.equals("")) {
199                     break;
200                 }
201                 try {
202                     Thread.sleep(100);
203                 } catch (InterruptedException e) {
204                     // Ignore
205                 }
206             }
207         }
208     }
209 
writeLogpersistOption(Object newValue, boolean skipWarning)210     public void writeLogpersistOption(Object newValue, boolean skipWarning) {
211         if (mLogpersist == null) {
212             return;
213         }
214         String currentTag = SystemProperties.get(
215                 AbstractLogdSizePreferenceController.SELECT_LOGD_TAG_PROPERTY);
216         if ((currentTag != null) && currentTag.startsWith(
217                 AbstractLogdSizePreferenceController.SELECT_LOGD_TAG_SILENCE)) {
218             newValue = null;
219             skipWarning = true;
220         }
221 
222         if ((newValue == null) || newValue.toString().equals("")) {
223             if (skipWarning) {
224                 mLogpersistCleared = false;
225             } else if (!mLogpersistCleared) {
226                 // if transitioning from on to off, pop up an are you sure?
227                 String currentValue = SystemProperties.get(ACTUAL_LOGPERSIST_PROPERTY);
228                 if ((currentValue != null) &&
229                         currentValue.equals(SELECT_LOGPERSIST_PROPERTY_SERVICE)) {
230                     showConfirmationDialog(mLogpersist);
231                     return;
232                 }
233             }
234             setLogpersistOff(true);
235             return;
236         }
237 
238         String currentBuffer = SystemProperties.get(ACTUAL_LOGPERSIST_PROPERTY_BUFFER);
239         if ((currentBuffer != null) && !currentBuffer.equals(newValue.toString())) {
240             setLogpersistOff(false);
241         }
242         SystemProperties.set(SELECT_LOGPERSIST_PROPERTY_BUFFER, newValue.toString());
243         SystemProperties.set(SELECT_LOGPERSIST_PROPERTY, SELECT_LOGPERSIST_PROPERTY_SERVICE);
244         SystemPropPoker.getInstance().poke();
245         for (int i = 0; i < 3; i++) {
246             String currentValue = SystemProperties.get(ACTUAL_LOGPERSIST_PROPERTY);
247             if ((currentValue != null)
248                     && currentValue.equals(SELECT_LOGPERSIST_PROPERTY_SERVICE)) {
249                 break;
250             }
251             try {
252                 Thread.sleep(100);
253             } catch (InterruptedException e) {
254                 // Ignore
255             }
256         }
257         updateLogpersistValues();
258     }
259 }
260