1 /*
2  * Copyright (C) 2009 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.cts.appwithdata;
18 
19 import android.content.ContentValues;
20 import android.content.Context;
21 import android.content.SharedPreferences;
22 import android.database.Cursor;
23 import android.database.sqlite.SQLiteDatabase;
24 import android.database.sqlite.SQLiteOpenHelper;
25 import android.net.TrafficStats;
26 import android.test.AndroidTestCase;
27 import android.util.Log;
28 
29 import java.net.ServerSocket;
30 import java.net.Socket;
31 
32 import java.io.BufferedReader;
33 import java.io.DataInputStream;
34 import java.io.DataOutputStream;
35 import java.io.FileInputStream;
36 import java.io.FileNotFoundException;
37 import java.io.FileOutputStream;
38 import java.io.FileReader;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.io.OutputStream;
42 
43 /**
44  * Test that will create private app data.
45  *
46  * This is not really a test per-say. Its just used as a hook so the test controller can trigger
47  * the creation of private app data.
48  */
49 public class CreatePrivateDataTest extends AndroidTestCase {
50 
51     /**
52      * The Android package name of the application that owns the private data
53      */
54     private static final String APP_WITH_DATA_PKG = "com.android.cts.appwithdata";
55 
56     /**
57      * Name of private file to create.
58      */
59     private static final String PRIVATE_FILE_NAME = "private_file.txt";
60     private static final String PUBLIC_FILE_NAME = "public_file.txt";
61 
62     private static final String PREFERENCES_FILE_NAME = "preferences";
63     private static final String PREFERENCE_KEY = "preference_key";
64     private static final String PREFERENCE_VALUE = "preference_value";
65 
66     static final String DB_TABLE_NAME = "test_table";
67     static final String DB_COLUMN = "test_column";
68     static final String DB_VALUE = "test_value";
69 
70     /**
71      * Creates the private data for this app, which includes
72      * file, database entries, and traffic stats.
73      * @throws IOException if any error occurred when creating the file
74      */
testCreatePrivateData()75     public void testCreatePrivateData() throws IOException {
76         FileOutputStream outputStream = getContext().openFileOutput(PRIVATE_FILE_NAME,
77                 Context.MODE_PRIVATE);
78         outputStream.write("file contents".getBytes());
79         outputStream.close();
80         assertTrue(getContext().getFileStreamPath(PRIVATE_FILE_NAME).exists());
81 
82         outputStream = getContext().openFileOutput(PUBLIC_FILE_NAME,
83                 Context.MODE_WORLD_READABLE);
84         DataOutputStream dataOut = new DataOutputStream(outputStream);
85         dataOut.writeInt(getContext().getApplicationInfo().uid);
86         dataOut.close();
87         outputStream.close();
88         // Ensure that some file will be accessible via the same path that will be used by other app.
89         accessPublicData();
90 
91         writeToPreferences();
92         writeToDatabase();
93         createTrafficStatsWithTags();
94     }
95 
accessPublicData()96     private void accessPublicData() throws IOException {
97         try {
98             // construct the absolute file path to the app's public's file the same
99             // way as the appaccessdata package will.
100             String publicFilePath = String.format("/data/data/%s/files/%s", APP_WITH_DATA_PKG,
101                     PUBLIC_FILE_NAME);
102             DataInputStream inputStream = new DataInputStream(new FileInputStream(publicFilePath));
103             int otherAppUid = (int)inputStream.readInt();
104             inputStream.close();
105         } catch (FileNotFoundException e) {
106             fail("Was not able to access own public file: " + e);
107         } catch (SecurityException e) {
108             fail("Was not able to access own public file: " + e);
109         }
110     }
111 
writeToPreferences()112     private void writeToPreferences() {
113         SharedPreferences prefs = mContext.getSharedPreferences(PREFERENCES_FILE_NAME, 0);
114         SharedPreferences.Editor editor = prefs.edit();
115         editor.putString(PREFERENCE_KEY, PREFERENCE_VALUE);
116         editor.commit();
117         assertEquals(PREFERENCE_VALUE, prefs.getString(PREFERENCE_KEY, null));
118     }
119 
writeToDatabase()120     private void writeToDatabase() {
121         SQLiteDatabase db = null;
122         Cursor cursor = null;
123         try {
124             db = new TestDatabaseOpenHelper(mContext).getWritableDatabase();
125             ContentValues values = new ContentValues(1);
126             values.put(DB_COLUMN, DB_VALUE);
127             assertTrue(db.insert(DB_TABLE_NAME, null, values) != -1);
128 
129             cursor = db.query(DB_TABLE_NAME, new String[] {DB_COLUMN},
130                     null, null, null, null, null);
131             assertEquals(1, cursor.getCount());
132         } finally {
133             if (cursor != null) {
134                 cursor.close();
135             }
136             if (db != null) {
137                 db.close();
138             }
139         }
140     }
141 
142     /**
143      * Check to ensure the private file created in testCreatePrivateData does not exist.
144      * Used to check that uninstall of an app deletes the app's data.
145      */
testEnsurePrivateDataNotExist()146     public void testEnsurePrivateDataNotExist() throws IOException {
147         assertFalse(getContext().getFileStreamPath(PRIVATE_FILE_NAME).exists());
148 
149         assertPreferencesDataDoesNotExist();
150         assertDatabaseDataDoesNotExist();
151     }
152 
assertPreferencesDataDoesNotExist()153     private void assertPreferencesDataDoesNotExist() {
154         SharedPreferences prefs = mContext.getSharedPreferences(PREFERENCES_FILE_NAME, 0);
155         assertNull(prefs.getString(PREFERENCE_KEY, null));
156     }
157 
assertDatabaseDataDoesNotExist()158     private void assertDatabaseDataDoesNotExist() {
159         SQLiteDatabase db = null;
160         Cursor cursor = null;
161         try {
162             db = new TestDatabaseOpenHelper(mContext).getWritableDatabase();
163             cursor = db.query(DB_TABLE_NAME, new String[] {DB_COLUMN},
164                     null, null, null, null, null);
165             assertEquals(0, cursor.getCount());
166         } finally {
167             if (cursor != null) {
168                 cursor.close();
169             }
170             if (db != null) {
171                 db.close();
172             }
173         }
174     }
175 
accessOwnTrafficStats()176     private void accessOwnTrafficStats() throws IOException {
177         final int ownAppUid = getContext().getApplicationInfo().uid;
178 
179         boolean foundOwnDetailedStats = false;
180         try {
181             BufferedReader qtaguidReader = new BufferedReader(new FileReader("/proc/net/xt_qtaguid/stats"));
182             String line;
183             while ((line = qtaguidReader.readLine()) != null) {
184                 String tokens[] = line.split(" ");
185                 if (tokens.length > 3 && tokens[3].equals(String.valueOf(ownAppUid))) {
186                     if (!tokens[2].equals("0x0")) {
187                       foundOwnDetailedStats = true;
188                     }
189                 }
190             }
191             qtaguidReader.close();
192         } catch (FileNotFoundException e) {
193             fail("Was not able to access qtaguid/stats: " + e);
194         }
195         assertTrue("Was expecting to find own traffic stats", foundOwnDetailedStats);
196     }
197 
createTrafficStatsWithTags()198     private void createTrafficStatsWithTags() throws IOException {
199 
200         // Transfer 1MB of data across an explicitly localhost socket.
201         final int byteCount = 1024;
202         final int packetCount = 1024;
203 
204         final ServerSocket server = new ServerSocket(0);
205         new Thread("CreatePrivateDataTest.createTrafficStatsWithTags") {
206             @Override
207             public void run() {
208                 try {
209                     Socket socket = new Socket("localhost", server.getLocalPort());
210                     // Make sure that each write()+flush() turns into a packet:
211                     // disable Nagle.
212                     socket.setTcpNoDelay(true);
213                     OutputStream out = socket.getOutputStream();
214                     byte[] buf = new byte[byteCount];
215                     for (int i = 0; i < packetCount; i++) {
216                         TrafficStats.setThreadStatsTag(i % 10);
217                         TrafficStats.tagSocket(socket);
218                         out.write(buf);
219                         out.flush();
220                     }
221                     out.close();
222                     socket.close();
223                 } catch (IOException e) {
224                   assertTrue("io exception" + e, false);
225                 }
226             }
227         }.start();
228 
229         try {
230             Socket socket = server.accept();
231             InputStream in = socket.getInputStream();
232             byte[] buf = new byte[byteCount];
233             int read = 0;
234             while (read < byteCount * packetCount) {
235                 int n = in.read(buf);
236                 assertTrue("Unexpected EOF", n > 0);
237                 read += n;
238             }
239         } finally {
240             server.close();
241         }
242 
243         accessOwnTrafficStats();
244     }
245 
246     static class TestDatabaseOpenHelper extends SQLiteOpenHelper {
247 
248         static final String _ID = "_id";
249 
TestDatabaseOpenHelper(Context context)250         public TestDatabaseOpenHelper(Context context) {
251             super(context, "test.db", null, 1337);
252         }
253 
254         @Override
onCreate(SQLiteDatabase db)255         public void onCreate(SQLiteDatabase db) {
256             db.execSQL("CREATE TABLE " + DB_TABLE_NAME + " ("
257                     + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
258                     + DB_COLUMN + " TEXT);");
259         }
260 
261         @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)262         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
263             db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE_NAME);
264             onCreate(db);
265         }
266     }
267 }
268