blob: e11681ae118602b54306dc8210977b0b49acb942 [file] [log] [blame]
/*
* 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);
}
}
}