/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.cts.appwithdata; import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.net.TrafficStats; import android.test.AndroidTestCase; import android.util.Log; import java.net.ServerSocket; import java.net.Socket; import java.io.BufferedReader; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * Test that will create private app data. * * This is not really a test per-say. Its just used as a hook so the test controller can trigger * the creation of private app data. */ public class CreatePrivateDataTest extends AndroidTestCase { /** * The Android package name of the application that owns the private data */ private static final String APP_WITH_DATA_PKG = "com.android.cts.appwithdata"; /** * Name of private file to create. */ private static final String PRIVATE_FILE_NAME = "private_file.txt"; private static final String PUBLIC_FILE_NAME = "public_file.txt"; private static final String PREFERENCES_FILE_NAME = "preferences"; private static final String PREFERENCE_KEY = "preference_key"; private static final String PREFERENCE_VALUE = "preference_value"; static final String DB_TABLE_NAME = "test_table"; static final String DB_COLUMN = "test_column"; static final String DB_VALUE = "test_value"; /** * Creates the private data for this app, which includes * file, database entries, and traffic stats. * @throws IOException if any error occurred when creating the file */ public void testCreatePrivateData() throws IOException { FileOutputStream outputStream = getContext().openFileOutput(PRIVATE_FILE_NAME, Context.MODE_PRIVATE); outputStream.write("file contents".getBytes()); outputStream.close(); assertTrue(getContext().getFileStreamPath(PRIVATE_FILE_NAME).exists()); outputStream = getContext().openFileOutput(PUBLIC_FILE_NAME, Context.MODE_WORLD_READABLE); DataOutputStream dataOut = new DataOutputStream(outputStream); dataOut.writeInt(getContext().getApplicationInfo().uid); dataOut.close(); outputStream.close(); // Ensure that some file will be accessible via the same path that will be used by other app. accessPublicData(); writeToPreferences(); writeToDatabase(); createTrafficStatsWithTags(); } private void accessPublicData() throws IOException { try { // construct the absolute file path to the app's public's file the same // way as the appaccessdata package will. String publicFilePath = String.format("/data/data/%s/files/%s", APP_WITH_DATA_PKG, PUBLIC_FILE_NAME); DataInputStream inputStream = new DataInputStream(new FileInputStream(publicFilePath)); int otherAppUid = (int)inputStream.readInt(); inputStream.close(); } catch (FileNotFoundException e) { fail("Was not able to access own public file: " + e); } catch (SecurityException e) { fail("Was not able to access own public file: " + e); } } private void writeToPreferences() { SharedPreferences prefs = mContext.getSharedPreferences(PREFERENCES_FILE_NAME, 0); SharedPreferences.Editor editor = prefs.edit(); editor.putString(PREFERENCE_KEY, PREFERENCE_VALUE); editor.commit(); assertEquals(PREFERENCE_VALUE, prefs.getString(PREFERENCE_KEY, null)); } private void writeToDatabase() { SQLiteDatabase db = null; Cursor cursor = null; try { db = new TestDatabaseOpenHelper(mContext).getWritableDatabase(); ContentValues values = new ContentValues(1); values.put(DB_COLUMN, DB_VALUE); assertTrue(db.insert(DB_TABLE_NAME, null, values) != -1); cursor = db.query(DB_TABLE_NAME, new String[] {DB_COLUMN}, null, null, null, null, null); assertEquals(1, cursor.getCount()); } finally { if (cursor != null) { cursor.close(); } if (db != null) { db.close(); } } } /** * Check to ensure the private file created in testCreatePrivateData does not exist. * Used to check that uninstall of an app deletes the app's data. */ public void testEnsurePrivateDataNotExist() throws IOException { assertFalse(getContext().getFileStreamPath(PRIVATE_FILE_NAME).exists()); assertPreferencesDataDoesNotExist(); assertDatabaseDataDoesNotExist(); } private void assertPreferencesDataDoesNotExist() { SharedPreferences prefs = mContext.getSharedPreferences(PREFERENCES_FILE_NAME, 0); assertNull(prefs.getString(PREFERENCE_KEY, null)); } private void assertDatabaseDataDoesNotExist() { SQLiteDatabase db = null; Cursor cursor = null; try { db = new TestDatabaseOpenHelper(mContext).getWritableDatabase(); cursor = db.query(DB_TABLE_NAME, new String[] {DB_COLUMN}, null, null, null, null, null); assertEquals(0, cursor.getCount()); } finally { if (cursor != null) { cursor.close(); } if (db != null) { db.close(); } } } private void accessOwnTrafficStats() throws IOException { final int ownAppUid = getContext().getApplicationInfo().uid; boolean foundOwnDetailedStats = false; try { BufferedReader qtaguidReader = new BufferedReader(new FileReader("/proc/net/xt_qtaguid/stats")); String line; while ((line = qtaguidReader.readLine()) != null) { String tokens[] = line.split(" "); if (tokens.length > 3 && tokens[3].equals(String.valueOf(ownAppUid))) { if (!tokens[2].equals("0x0")) { foundOwnDetailedStats = true; } } } qtaguidReader.close(); } catch (FileNotFoundException e) { fail("Was not able to access qtaguid/stats: " + e); } assertTrue("Was expecting to find own traffic stats", foundOwnDetailedStats); } private void createTrafficStatsWithTags() throws IOException { // Transfer 1MB of data across an explicitly localhost socket. final int byteCount = 1024; final int packetCount = 1024; final ServerSocket server = new ServerSocket(0); new Thread("CreatePrivateDataTest.createTrafficStatsWithTags") { @Override public void run() { try { Socket socket = new Socket("localhost", server.getLocalPort()); // Make sure that each write()+flush() turns into a packet: // disable Nagle. socket.setTcpNoDelay(true); OutputStream out = socket.getOutputStream(); byte[] buf = new byte[byteCount]; for (int i = 0; i < packetCount; i++) { TrafficStats.setThreadStatsTag(i % 10); TrafficStats.tagSocket(socket); out.write(buf); out.flush(); } out.close(); socket.close(); } catch (IOException e) { assertTrue("io exception" + e, false); } } }.start(); try { Socket socket = server.accept(); InputStream in = socket.getInputStream(); byte[] buf = new byte[byteCount]; int read = 0; while (read < byteCount * packetCount) { int n = in.read(buf); assertTrue("Unexpected EOF", n > 0); read += n; } } finally { server.close(); } accessOwnTrafficStats(); } static class TestDatabaseOpenHelper extends SQLiteOpenHelper { static final String _ID = "_id"; public TestDatabaseOpenHelper(Context context) { super(context, "test.db", null, 1337); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE " + DB_TABLE_NAME + " (" + _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + DB_COLUMN + " TEXT);"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE_NAME); onCreate(db); } } }