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 
28 import java.io.File;
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, 0 /*mode*/);
83         DataOutputStream dataOut = new DataOutputStream(outputStream);
84         dataOut.writeInt(getContext().getApplicationInfo().uid);
85         dataOut.close();
86         outputStream.close();
87         // Ensure that some file will be accessible via the same path that will be used by other app.
88         accessPublicData();
89 
90         writeToPreferences();
91         writeToDatabase();
92         createTrafficStatsWithTags();
93     }
94 
accessPublicData()95     private void accessPublicData() throws IOException {
96         try {
97             // construct the absolute file path to the app's public's file the same
98             // way as the appaccessdata package will.
99             File publicFile = new File(mContext.getFilesDir(), PUBLIC_FILE_NAME);
100             DataInputStream inputStream = new DataInputStream(new FileInputStream(publicFile));
101             inputStream.readInt();
102             inputStream.close();
103         } catch (FileNotFoundException | SecurityException e) {
104             fail("Was not able to access own public file: " + e);
105         }
106     }
107 
writeToPreferences()108     private void writeToPreferences() {
109         SharedPreferences prefs = mContext.getSharedPreferences(PREFERENCES_FILE_NAME, 0);
110         SharedPreferences.Editor editor = prefs.edit();
111         editor.putString(PREFERENCE_KEY, PREFERENCE_VALUE);
112         editor.commit();
113         assertEquals(PREFERENCE_VALUE, prefs.getString(PREFERENCE_KEY, null));
114     }
115 
writeToDatabase()116     private void writeToDatabase() {
117         SQLiteDatabase db = null;
118         Cursor cursor = null;
119         try {
120             db = new TestDatabaseOpenHelper(mContext).getWritableDatabase();
121             ContentValues values = new ContentValues(1);
122             values.put(DB_COLUMN, DB_VALUE);
123             assertTrue(db.insert(DB_TABLE_NAME, null, values) != -1);
124 
125             cursor = db.query(DB_TABLE_NAME, new String[] {DB_COLUMN},
126                     null, null, null, null, null);
127             assertEquals(1, cursor.getCount());
128         } finally {
129             if (cursor != null) {
130                 cursor.close();
131             }
132             if (db != null) {
133                 db.close();
134             }
135         }
136     }
137 
138     /**
139      * Check to ensure the private file created in testCreatePrivateData does not exist.
140      * Used to check that uninstall of an app deletes the app's data.
141      */
testEnsurePrivateDataNotExist()142     public void testEnsurePrivateDataNotExist() throws IOException {
143         assertFalse(getContext().getFileStreamPath(PRIVATE_FILE_NAME).exists());
144 
145         assertPreferencesDataDoesNotExist();
146         assertDatabaseDataDoesNotExist();
147     }
148 
assertPreferencesDataDoesNotExist()149     private void assertPreferencesDataDoesNotExist() {
150         SharedPreferences prefs = mContext.getSharedPreferences(PREFERENCES_FILE_NAME, 0);
151         assertNull(prefs.getString(PREFERENCE_KEY, null));
152     }
153 
assertDatabaseDataDoesNotExist()154     private void assertDatabaseDataDoesNotExist() {
155         SQLiteDatabase db = null;
156         Cursor cursor = null;
157         try {
158             db = new TestDatabaseOpenHelper(mContext).getWritableDatabase();
159             cursor = db.query(DB_TABLE_NAME, new String[] {DB_COLUMN},
160                     null, null, null, null, null);
161             assertEquals(0, cursor.getCount());
162         } finally {
163             if (cursor != null) {
164                 cursor.close();
165             }
166             if (db != null) {
167                 db.close();
168             }
169         }
170     }
171 
accessOwnTrafficStats()172     private void accessOwnTrafficStats() throws IOException {
173         final int ownAppUid = getContext().getApplicationInfo().uid;
174 
175         boolean foundOwnDetailedStats = false;
176         try {
177             BufferedReader qtaguidReader = new BufferedReader(new FileReader("/proc/net/xt_qtaguid/stats"));
178             String line;
179             while ((line = qtaguidReader.readLine()) != null) {
180                 String tokens[] = line.split(" ");
181                 if (tokens.length > 3 && tokens[3].equals(String.valueOf(ownAppUid))) {
182                     if (!tokens[2].equals("0x0")) {
183                       foundOwnDetailedStats = true;
184                     }
185                 }
186             }
187             qtaguidReader.close();
188         } catch (FileNotFoundException e) {
189             fail("Was not able to access qtaguid/stats: " + e);
190         }
191         assertTrue("Was expecting to find own traffic stats", foundOwnDetailedStats);
192     }
193 
createTrafficStatsWithTags()194     private void createTrafficStatsWithTags() throws IOException {
195 
196         // Transfer 1MB of data across an explicitly localhost socket.
197         final int byteCount = 1024;
198         final int packetCount = 1024;
199 
200         final ServerSocket server = new ServerSocket(0);
201         new Thread("CreatePrivateDataTest.createTrafficStatsWithTags") {
202             @Override
203             public void run() {
204                 try {
205                     Socket socket = new Socket("localhost", server.getLocalPort());
206                     // Make sure that each write()+flush() turns into a packet:
207                     // disable Nagle.
208                     socket.setTcpNoDelay(true);
209                     OutputStream out = socket.getOutputStream();
210                     byte[] buf = new byte[byteCount];
211                     for (int i = 0; i < packetCount; i++) {
212                         TrafficStats.setThreadStatsTag(i % 10);
213                         TrafficStats.tagSocket(socket);
214                         out.write(buf);
215                         out.flush();
216                     }
217                     out.close();
218                     socket.close();
219                 } catch (IOException e) {
220                   assertTrue("io exception" + e, false);
221                 }
222             }
223         }.start();
224 
225         try {
226             Socket socket = server.accept();
227             InputStream in = socket.getInputStream();
228             byte[] buf = new byte[byteCount];
229             int read = 0;
230             while (read < byteCount * packetCount) {
231                 int n = in.read(buf);
232                 assertTrue("Unexpected EOF", n > 0);
233                 read += n;
234             }
235         } finally {
236             server.close();
237         }
238 
239         accessOwnTrafficStats();
240     }
241 
242     static class TestDatabaseOpenHelper extends SQLiteOpenHelper {
243 
244         static final String _ID = "_id";
245 
TestDatabaseOpenHelper(Context context)246         public TestDatabaseOpenHelper(Context context) {
247             super(context, "test.db", null, 1337);
248         }
249 
250         @Override
onCreate(SQLiteDatabase db)251         public void onCreate(SQLiteDatabase db) {
252             db.execSQL("CREATE TABLE " + DB_TABLE_NAME + " ("
253                     + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
254                     + DB_COLUMN + " TEXT);");
255         }
256 
257         @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)258         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
259             db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE_NAME);
260             onCreate(db);
261         }
262     }
263 }
264