1 /*
2  * Copyright (C) 2008 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 android.provider.cts.settings;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertNotNull;
21 import static org.junit.Assert.assertNull;
22 import static org.junit.Assert.assertSame;
23 import static org.junit.Assert.assertTrue;
24 import static org.junit.Assert.fail;
25 
26 import android.app.Instrumentation;
27 import android.content.ContentProviderClient;
28 import android.content.ContentResolver;
29 import android.content.ContentValues;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.pm.PackageManager;
33 import android.content.pm.ResolveInfo;
34 import android.database.ContentObserver;
35 import android.database.Cursor;
36 import android.database.sqlite.SQLiteException;
37 import android.net.Uri;
38 import android.os.Binder;
39 import android.os.Build;
40 import android.os.Handler;
41 import android.os.HandlerThread;
42 import android.os.Process;
43 import android.os.RemoteException;
44 import android.os.SystemClock;
45 import android.provider.Settings;
46 
47 import androidx.test.ext.junit.runners.AndroidJUnit4;
48 import androidx.test.filters.SdkSuppress;
49 import androidx.test.platform.app.InstrumentationRegistry;
50 
51 import com.android.compatibility.common.util.SystemUtil;
52 
53 import org.junit.AfterClass;
54 import org.junit.BeforeClass;
55 import org.junit.Ignore;
56 import org.junit.Test;
57 import org.junit.runner.RunWith;
58 
59 import java.util.concurrent.CountDownLatch;
60 import java.util.concurrent.TimeUnit;
61 
62 @RunWith(AndroidJUnit4.class)
63 public class SettingsTest {
64 
65     private static final int sUserId = Process.myUserHandle().getIdentifier();
66     private static final String sPackageName =
67             InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName();
68     @BeforeClass
setUp()69     public static void setUp() throws Exception {
70         InstrumentationRegistry.getInstrumentation().getUiAutomation()
71                 .executeShellCommand("appops set --user " + sUserId + " " + sPackageName
72                         + " android:write_settings allow");
73 
74         // Wait a beat to persist the change
75         SystemClock.sleep(500);
76     }
77 
78     @AfterClass
tearDown()79     public static void tearDown() throws Exception {
80         InstrumentationRegistry.getInstrumentation().getUiAutomation()
81                 .executeShellCommand("appops set --user " + sUserId + " " + sPackageName
82                         + " android:write_settings default");
83     }
84 
85     @Test
testSystemTable()86     public void testSystemTable() throws RemoteException {
87         final String[] SYSTEM_PROJECTION = new String[] {
88                 Settings.System._ID, Settings.System.NAME, Settings.System.VALUE
89         };
90         final int NAME_INDEX = 1;
91         final int VALUE_INDEX = 2;
92 
93         String name = Settings.System.NEXT_ALARM_FORMATTED;
94         String insertValue = "value_insert";
95         String updateValue = "value_update";
96 
97         // get provider
98         ContentResolver cr = getContext().getContentResolver();
99         ContentProviderClient provider =
100                 cr.acquireContentProviderClient(Settings.System.CONTENT_URI);
101         Cursor cursor = null;
102 
103         try {
104             // Test: insert
105             ContentValues value = new ContentValues();
106             value.put(Settings.System.NAME, name);
107             value.put(Settings.System.VALUE, insertValue);
108 
109             provider.insert(Settings.System.CONTENT_URI, value);
110             cursor = provider.query(Settings.System.CONTENT_URI, SYSTEM_PROJECTION,
111                     Settings.System.NAME + "=\"" + name + "\"", null, null, null);
112             assertNotNull(cursor);
113             assertEquals(1, cursor.getCount());
114             assertTrue(cursor.moveToFirst());
115             assertEquals(name, cursor.getString(NAME_INDEX));
116             assertEquals(insertValue, cursor.getString(VALUE_INDEX));
117             cursor.close();
118             cursor = null;
119 
120             // Test: update
121             value.clear();
122             value.put(Settings.System.NAME, name);
123             value.put(Settings.System.VALUE, updateValue);
124 
125             provider.update(Settings.System.CONTENT_URI, value,
126                     Settings.System.NAME + "=\"" + name + "\"", null);
127             cursor = provider.query(Settings.System.CONTENT_URI, SYSTEM_PROJECTION,
128                     Settings.System.NAME + "=\"" + name + "\"", null, null, null);
129             assertNotNull(cursor);
130             assertEquals(1, cursor.getCount());
131             assertTrue(cursor.moveToFirst());
132             assertEquals(name, cursor.getString(NAME_INDEX));
133             assertEquals(updateValue, cursor.getString(VALUE_INDEX));
134             cursor.close();
135             cursor = null;
136         } finally {
137             if (cursor != null) {
138                 cursor.close();
139             }
140         }
141     }
142 
143     @Test
testSecureTable()144     public void testSecureTable() throws Exception {
145         final String[] SECURE_PROJECTION = new String[] {
146                 Settings.Secure._ID, Settings.Secure.NAME, Settings.Secure.VALUE
147         };
148 
149         ContentResolver cr = getContext().getContentResolver();
150         ContentProviderClient provider =
151                 cr.acquireContentProviderClient(Settings.Secure.CONTENT_URI);
152         assertNotNull(provider);
153 
154         // Test that the secure table can be read from.
155         Cursor cursor = null;
156         try {
157             cursor = provider.query(Settings.Global.CONTENT_URI, SECURE_PROJECTION,
158                     Settings.Global.NAME + "=\"" + Settings.Global.ADB_ENABLED + "\"",
159                     null, null, null);
160             assertNotNull(cursor);
161         } finally {
162             if (cursor != null) {
163                 cursor.close();
164             }
165         }
166     }
167 
168     private static final String[] SELECT_VALUE =
169         new String[] { Settings.NameValueTable.VALUE };
170     private static final String NAME_EQ_PLACEHOLDER = "name=?";
171 
tryBadTableAccess(String table, String goodtable, String name)172     private void tryBadTableAccess(String table, String goodtable, String name) {
173         ContentResolver cr = getContext().getContentResolver();
174 
175         Uri uri = Uri.parse("content://settings/" + table);
176         ContentValues cv = new ContentValues();
177         cv.put("name", "name");
178         cv.put("value", "xxxTESTxxx");
179 
180         try {
181             cr.insert(uri, cv);
182             fail("SettingsProvider didn't throw IllegalArgumentException for insert name "
183                     + name + " at URI " + uri);
184         } catch (IllegalArgumentException e) {
185             /* ignore */
186         }
187 
188         try {
189             cr.update(uri, cv, NAME_EQ_PLACEHOLDER, new String[]{name});
190             fail("SettingsProvider didn't throw SecurityException for update name "
191                     + name + " at URI " + uri);
192         } catch (IllegalArgumentException e) {
193             /* ignore */
194         }
195 
196         try {
197             cr.query(uri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
198                     new String[]{name}, null);
199             fail("SettingsProvider didn't throw IllegalArgumentException for query name "
200                     + name + " at URI " + uri);
201         } catch (IllegalArgumentException e) {
202             /* ignore */
203         }
204 
205 
206         try {
207             cr.delete(uri, NAME_EQ_PLACEHOLDER, new String[]{name});
208             fail("SettingsProvider didn't throw IllegalArgumentException for delete name "
209                     + name + " at URI " + uri);
210         } catch (IllegalArgumentException e) {
211             /* ignore */
212         }
213 
214 
215         String mimeType = cr.getType(uri);
216         assertNull("SettingsProvider didn't return null MIME type for getType at URI "
217                 + uri, mimeType);
218 
219         uri = Uri.parse("content://settings/" + goodtable);
220         try {
221             Cursor c = cr.query(uri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
222                     new String[]{name}, null);
223             assertNotNull(c);
224             String value = c.moveToNext() ? c.getString(0) : null;
225             if ("xxxTESTxxx".equals(value)) {
226                 fail("Successfully modified " + name + " at URI " + uri);
227             }
228             c.close();
229         } catch (SQLiteException e) {
230             // This is fine.
231         }
232     }
233 
234     @Test
testAccessNonTable()235     public void testAccessNonTable() {
236         tryBadTableAccess("SYSTEM", "system", "install_non_market_apps");
237         tryBadTableAccess("SECURE", "secure", "install_non_market_apps");
238         tryBadTableAccess(" secure", "secure", "install_non_market_apps");
239         tryBadTableAccess("secure ", "secure", "install_non_market_apps");
240         tryBadTableAccess(" secure ", "secure", "install_non_market_apps");
241     }
242 
243     @Test
testUserDictionarySettingsExists()244     public void testUserDictionarySettingsExists() throws RemoteException {
245         final Intent intent = new Intent(Settings.ACTION_USER_DICTIONARY_SETTINGS);
246         final ResolveInfo ri = getContext().getPackageManager().resolveActivity(
247                 intent, PackageManager.MATCH_DEFAULT_ONLY);
248         assertTrue(ri != null);
249     }
250 
251     @Test
testNoStaleValueModifiedFromSameProcess()252     public void testNoStaleValueModifiedFromSameProcess() throws Exception {
253         final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
254                 Settings.System.VIBRATE_WHEN_RINGING);
255         try {
256             for (int i = 0; i < 100; i++) {
257                 final int expectedValue = i % 2;
258                 Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
259                         Settings.System.VIBRATE_WHEN_RINGING, expectedValue);
260                 final int actualValue = Settings.System.getInt(getContext().getContentResolver(),
261                         Settings.System.VIBRATE_WHEN_RINGING);
262                 assertSame("Settings write must be atomic", expectedValue, actualValue);
263             }
264         } finally {
265             Settings.System.putInt(getContext().getContentResolver(),
266                     Settings.System.VIBRATE_WHEN_RINGING, initialValue);
267         }
268     }
269 
270     @Test
testNoStaleValueModifiedFromOtherProcess()271     public void testNoStaleValueModifiedFromOtherProcess() throws Exception {
272         final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
273                 Settings.System.VIBRATE_WHEN_RINGING);
274         try {
275             for (int i = 0; i < 20; i++) {
276                 final int expectedValue = i % 2;
277                 SystemUtil.runShellCommand(getInstrumentation(), "settings put --user " + sUserId
278                         + " system " +  Settings.System.VIBRATE_WHEN_RINGING + " " + expectedValue);
279                 final int actualValue = Settings.System.getInt(getContext().getContentResolver(),
280                         Settings.System.VIBRATE_WHEN_RINGING);
281                 assertSame("Settings write must be atomic", expectedValue, actualValue);
282             }
283         } finally {
284             Settings.System.putInt(getContext().getContentResolver(),
285                     Settings.System.VIBRATE_WHEN_RINGING, initialValue);
286         }
287     }
288 
289     @Test
testNoStaleValueModifiedFromMultipleProcesses()290     public void testNoStaleValueModifiedFromMultipleProcesses() throws Exception {
291         final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
292                 Settings.System.VIBRATE_WHEN_RINGING);
293         try {
294             for (int i = 0; i < 20; i++) {
295                 final int expectedValue = i % 2;
296                 final int unexpectedValue = (i + 1) % 2;
297                 Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
298                         Settings.System.VIBRATE_WHEN_RINGING, expectedValue);
299                 SystemUtil.runShellCommand(getInstrumentation(), "settings put --user " + sUserId
300                         + " system " +  Settings.System.VIBRATE_WHEN_RINGING + " "
301                         + unexpectedValue);
302                 Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
303                         Settings.System.VIBRATE_WHEN_RINGING, expectedValue);
304                 final int actualValue = Settings.System.getInt(getContext().getContentResolver(),
305                         Settings.System.VIBRATE_WHEN_RINGING);
306                 assertSame("Settings write must be atomic", expectedValue, actualValue);
307             }
308         } finally {
309             Settings.System.putInt(getContext().getContentResolver(),
310                     Settings.System.VIBRATE_WHEN_RINGING, initialValue);
311         }
312     }
313 
314     @Test
testUriChangesUpdatingFromDifferentProcesses()315     public void testUriChangesUpdatingFromDifferentProcesses() throws Exception {
316         final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
317                 Settings.System.VIBRATE_WHEN_RINGING);
318 
319         HandlerThread handlerThread = new HandlerThread("MyThread");
320         handlerThread.start();
321 
322         CountDownLatch uriChangeCount = new CountDownLatch(4);
323         Uri uri = Settings.System.getUriFor(Settings.System.VIBRATE_WHEN_RINGING);
324         getContext().getContentResolver().registerContentObserver(uri,
325                 false, new ContentObserver(new Handler(handlerThread.getLooper())) {
326                     @Override
327                     public void onChange(boolean selfChange) {
328                         uriChangeCount.countDown();
329                     }
330                 });
331 
332         try {
333             final int anotherValue = initialValue == 1 ? 0 : 1;
334             Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
335                     Settings.System.VIBRATE_WHEN_RINGING, anotherValue);
336             SystemUtil.runShellCommand(getInstrumentation(), "settings put --user " + sUserId
337                     + " system " +  Settings.System.VIBRATE_WHEN_RINGING + " " + initialValue);
338             Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
339                     Settings.System.VIBRATE_WHEN_RINGING, anotherValue);
340             Settings.System.getInt(getContext().getContentResolver(),
341                     Settings.System.VIBRATE_WHEN_RINGING);
342             SystemUtil.runShellCommand(getInstrumentation(), "settings put --user " + sUserId
343                     + " system " +  Settings.System.VIBRATE_WHEN_RINGING + " " + initialValue);
344 
345             uriChangeCount.await(30000, TimeUnit.MILLISECONDS);
346 
347             if (uriChangeCount.getCount() > 0) {
348                 fail("Expected change not received for Uri: " + uri);
349             }
350         } finally {
351             Settings.System.putInt(getContext().getContentResolver(),
352                     Settings.System.VIBRATE_WHEN_RINGING, initialValue);
353             handlerThread.quit();
354         }
355     }
356 
357     @Test
testCheckWriteSettingsOperation()358     public void testCheckWriteSettingsOperation() throws Exception {
359         final int myUid = Binder.getCallingUid();
360         // Verify write settings permission.
361         Settings.checkAndNoteWriteSettingsOperation(getContext(), myUid, sPackageName,
362                 true /* throwException */);
363 
364         // Verify SecurityException throw if uid do not match callingPackage.
365         final int otherUid = myUid + 1;
366         try {
367             Settings.checkAndNoteWriteSettingsOperation(getContext(), otherUid, sPackageName,
368                     true /* throwException */);
369             fail("Expect SecurityException because uid " + otherUid + " do not belong to "
370                     + sPackageName);
371         } catch (SecurityException se) { }
372 
373         // Verify SecurityException throw if calling package do not have WRITE_SETTINGS permission.
374         try {
375             final String fakeCallingPackage = "android.provider.cts.fakepackagename";
376             Settings.checkAndNoteWriteSettingsOperation(getContext(), myUid, fakeCallingPackage,
377                     true /* throwException */);
378             fail("Expect throwing SecurityException due to no WRITE_SETTINGS permission");
379         } catch (SecurityException se) { }
380 
381     }
382 
383     @Ignore("b/229197836")
384     @Test
385     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
testDataRoamingAccessPermission()386     public void testDataRoamingAccessPermission() throws Exception {
387         try {
388             Settings.Global.getInt(getContext().getContentResolver(), Settings.Global.DATA_ROAMING);
389             fail("Expect throwing RuntimeException due readable maxTargetedSdk = S");
390         } catch (SecurityException e) {
391             // Expected.
392         }
393     }
394 
getInstrumentation()395     private Instrumentation getInstrumentation() {
396         return InstrumentationRegistry.getInstrumentation();
397     }
398 
getContext()399     private Context getContext() {
400         return InstrumentationRegistry.getInstrumentation().getTargetContext();
401     }
402 }
403