1/*
2 * Copyright (C) 2010 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
17package com.android.strictmodetest;
18
19import android.app.Activity;
20import android.content.ComponentName;
21import android.content.ContentQueryMap;
22import android.content.ContentResolver;
23import android.content.ContentValues;
24import android.content.Context;
25import android.content.IContentProvider;
26import android.content.Intent;
27import android.content.SharedPreferences;
28import android.content.ServiceConnection;
29import android.content.pm.PackageManager;
30import android.content.pm.ResolveInfo;
31import android.content.res.Configuration;
32import android.content.res.Resources;
33import android.database.Cursor;
34import android.database.SQLException;
35import android.database.sqlite.SQLiteDatabase;
36import android.net.LocalSocket;
37import android.net.LocalSocketAddress;
38import android.net.Uri;
39import android.os.Bundle;
40import android.os.Debug;
41import android.os.Handler;
42import android.os.IBinder;
43import android.os.Parcel;
44import android.os.RemoteException;
45import android.os.ServiceManager;
46import android.os.StrictMode;
47import android.os.SystemClock;
48import android.telephony.TelephonyManager;
49import android.text.TextUtils;
50import android.util.AndroidException;
51import android.util.Log;
52import android.view.View;
53import android.widget.Button;
54import android.widget.CheckBox;
55import android.widget.TextView;
56
57import dalvik.system.BlockGuard;
58
59import org.apache.http.HttpResponse;
60import org.apache.http.client.methods.HttpUriRequest;
61import org.apache.http.client.methods.HttpGet;
62import org.apache.http.impl.client.DefaultHttpClient;
63
64import java.io.File;
65import java.io.FileInputStream;
66import java.io.FileOutputStream;
67import java.io.IOException;
68import java.io.InputStream;
69import java.io.OutputStream;
70import java.io.RandomAccessFile;
71import java.net.InetAddress;
72import java.net.Socket;
73import java.net.URL;
74import java.util.ArrayList;
75
76public class StrictModeActivity extends Activity {
77
78    private static final String TAG = "StrictModeActivity";
79    private static final Uri SYSTEM_SETTINGS_URI = Uri.parse("content://settings/system");
80
81    private ContentResolver cr;
82
83    private final static class SimpleConnection implements ServiceConnection {
84        public IService stub = null;
85        public void onServiceConnected(ComponentName name, IBinder service) {
86            stub = IService.Stub.asInterface(service);
87            Log.v(TAG, "Service connected: " + name);
88        }
89        public void onServiceDisconnected(ComponentName name) {
90            stub = null;
91            Log.v(TAG, "Service disconnected: " + name);
92        }
93    }
94
95    private final SimpleConnection mLocalServiceConn = new SimpleConnection();
96    private final SimpleConnection mRemoteServiceConn = new SimpleConnection();
97
98    private SQLiteDatabase mDb;
99
100    /** Called when the activity is first created. */
101    @Override
102    public void onCreate(Bundle savedInstanceState) {
103        super.onCreate(savedInstanceState);
104        setContentView(R.layout.main);
105
106        cr = getContentResolver();
107        mDb = openOrCreateDatabase("foo.db", MODE_PRIVATE, null);
108
109        final Button readButton = (Button) findViewById(R.id.read_button);
110        readButton.setOnClickListener(new View.OnClickListener() {
111                public void onClick(View v) {
112                    SharedPreferences prefs = getSharedPreferences("foo", 0);
113                    try {
114                        Cursor c = null;
115                        try {
116                            c = mDb.rawQuery("SELECT * FROM foo", null);
117                        } finally {
118                            if (c != null) c.close();
119                        }
120                    } catch (android.database.sqlite.SQLiteException e) {
121                        Log.e(TAG, "SQLiteException: " + e);
122                    }
123                }
124            });
125
126        final Button writeButton = (Button) findViewById(R.id.write_button);
127        writeButton.setOnClickListener(new View.OnClickListener() {
128                public void onClick(View v) {
129                    mDb.execSQL("CREATE TABLE IF NOT EXISTS FOO (a INT)");
130                    SharedPreferences prefs = getSharedPreferences("foo", 0);
131                    prefs.edit().putLong("time", System.currentTimeMillis()).commit();
132                }
133            });
134
135        final Button writeLoopButton = (Button) findViewById(R.id.write_loop_button);
136        writeLoopButton.setOnClickListener(new View.OnClickListener() {
137                public void onClick(View v) {
138                    long startTime = SystemClock.uptimeMillis();
139                    int iters = 1000;
140                    BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
141                    for (int i = 0; i < iters; ++i) {
142                        policy.onWriteToDisk();
143                    }
144                    long endTime = SystemClock.uptimeMillis();
145                    Log.d(TAG, "Time for " + iters + ": " + (endTime - startTime) + ", avg=" +
146                          (endTime - startTime) / (double) iters);
147                }
148            });
149
150        final Button dnsButton = (Button) findViewById(R.id.dns_button);
151        dnsButton.setOnClickListener(new View.OnClickListener() {
152                public void onClick(View v) {
153                    Log.d(TAG, "Doing DNS lookup for www.l.google.com... "
154                          + "(may be cached by InetAddress)");
155                    try {
156                        InetAddress[] addrs = InetAddress.getAllByName("www.l.google.com");
157                        for (int i = 0; i < addrs.length; ++i) {
158                            Log.d(TAG, "got: " + addrs[i]);
159                        }
160                    } catch (java.net.UnknownHostException e) {
161                        Log.d(TAG, "DNS error: " + e);
162                    }
163
164                    // Now try a random hostname to evade libcore's
165                    // DNS caching.
166                    try {
167                        String random = "" + Math.random();
168                        random = random.substring(random.indexOf(".") + 1);
169                        String domain = random + ".livejournal.com";
170                        InetAddress addr = InetAddress.getByName(domain);
171                        Log.d(TAG, "for random domain " + domain + ": " + addr);
172                    } catch (java.net.UnknownHostException e) {
173                    }
174                }
175            });
176
177        final Button httpButton = (Button) findViewById(R.id.http_button);
178        httpButton.setOnClickListener(new View.OnClickListener() {
179                public void onClick(View v) {
180                    try {
181                        // Note: not using AndroidHttpClient, as that comes with its
182                        // own pre-StrictMode network-on-Looper thread check.  The
183                        // intent of this test is that we test the network stack's
184                        // instrumentation for StrictMode instead.
185                        DefaultHttpClient httpClient = new DefaultHttpClient();
186                        HttpResponse res = httpClient.execute(
187                            new HttpGet("http://www.android.com/favicon.ico"));
188                        Log.d(TAG, "Fetched http response: " + res);
189                    } catch (IOException e) {
190                        Log.d(TAG, "HTTP fetch error: " + e);
191                    }
192                }
193            });
194
195        final Button http2Button = (Button) findViewById(R.id.http2_button);
196        http2Button.setOnClickListener(new View.OnClickListener() {
197                public void onClick(View v) {
198                    try {
199                        // Usually this ends up tripping in DNS resolution,
200                        // so see http3Button below, which connects directly to an IP
201                        InputStream is = new URL("http://www.android.com/")
202                                .openConnection()
203                                .getInputStream();
204                        Log.d(TAG, "Got input stream: " + is);
205                    } catch (IOException e) {
206                        Log.d(TAG, "HTTP fetch error: " + e);
207                    }
208                }
209            });
210
211        final Button http3Button = (Button) findViewById(R.id.http3_button);
212        http3Button.setOnClickListener(new View.OnClickListener() {
213                public void onClick(View v) {
214                    try {
215                        // One of Google's web IPs, as of 2010-06-16....
216                        InputStream is = new URL("http://74.125.19.14/")
217                                .openConnection()
218                                .getInputStream();
219                        Log.d(TAG, "Got input stream: " + is);
220                    } catch (IOException e) {
221                        Log.d(TAG, "HTTP fetch error: " + e);
222                    }
223                }
224            });
225
226        final Button binderLocalButton = (Button) findViewById(R.id.binder_local_button);
227        binderLocalButton.setOnClickListener(new View.OnClickListener() {
228                public void onClick(View v) {
229                    try {
230                        boolean value = mLocalServiceConn.stub.doDiskWrite(123 /* dummy */);
231                        Log.d(TAG, "local writeToDisk returned: " + value);
232                    } catch (RemoteException e) {
233                        Log.d(TAG, "local binderButton error: " + e);
234                    }
235                }
236            });
237
238        final Button binderRemoteButton = (Button) findViewById(R.id.binder_remote_button);
239        binderRemoteButton.setOnClickListener(new View.OnClickListener() {
240                public void onClick(View v) {
241                    try {
242                        boolean value = mRemoteServiceConn.stub.doDiskWrite(1);
243                        Log.d(TAG, "remote writeToDisk #1 returned: " + value);
244                        value = mRemoteServiceConn.stub.doDiskWrite(2);
245                        Log.d(TAG, "remote writeToDisk #2 returned: " + value);
246                    } catch (RemoteException e) {
247                        Log.d(TAG, "remote binderButton error: " + e);
248                    }
249                }
250            });
251
252        final Button binderOneWayButton = (Button) findViewById(R.id.binder_oneway_button);
253        binderOneWayButton.setOnClickListener(new View.OnClickListener() {
254                public void onClick(View v) {
255                    try {
256                        Log.d(TAG, "doing oneway disk write over Binder.");
257                        mRemoteServiceConn.stub.doDiskOneWay();
258                    } catch (RemoteException e) {
259                        Log.d(TAG, "remote binderButton error: " + e);
260                    }
261                }
262            });
263
264        final Button binderCheckButton = (Button) findViewById(R.id.binder_check_button);
265        binderCheckButton.setOnClickListener(new View.OnClickListener() {
266                public void onClick(View v) {
267                    int policy;
268                    try {
269                        policy = mLocalServiceConn.stub.getThreadPolicy();
270                        Log.d(TAG, "local service policy: " + policy);
271                        policy = mRemoteServiceConn.stub.getThreadPolicy();
272                        Log.d(TAG, "remote service policy: " + policy);
273                    } catch (RemoteException e) {
274                        Log.d(TAG, "binderCheckButton error: " + e);
275                    }
276                }
277            });
278
279        final Button serviceDumpButton = (Button) findViewById(R.id.service_dump);
280        serviceDumpButton.setOnClickListener(new View.OnClickListener() {
281                public void onClick(View v) {
282                    Log.d(TAG, "About to do a service dump...");
283                    File file = new File("/sdcard/strictmode-service-dump.txt");
284                    FileOutputStream output = null;
285                    final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
286                    try {
287                        StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.LAX);
288                        output = new FileOutputStream(file);
289                        StrictMode.setThreadPolicy(oldPolicy);
290                        boolean dumped = Debug.dumpService("cpuinfo",
291                                                           output.getFD(), new String[0]);
292                        Log.d(TAG, "Dumped = " + dumped);
293                    } catch (IOException e) {
294                        Log.e(TAG, "Can't dump service", e);
295                    } finally {
296                        StrictMode.setThreadPolicy(oldPolicy);
297                    }
298                    Log.d(TAG, "Did service dump.");
299                }
300            });
301
302        final Button lingerCloseButton = (Button) findViewById(R.id.linger_close_button);
303        lingerCloseButton.setOnClickListener(new View.OnClickListener() {
304                public void onClick(View v) {
305                    closeWithLinger(true);
306                }
307            });
308
309        final Button nonlingerCloseButton = (Button) findViewById(R.id.nonlinger_close_button);
310        nonlingerCloseButton.setOnClickListener(new View.OnClickListener() {
311                public void onClick(View v) {
312                    closeWithLinger(false);
313                }
314            });
315
316        final Button leakCursorButton = (Button) findViewById(R.id.leak_cursor_button);
317        leakCursorButton.setOnClickListener(new View.OnClickListener() {
318                public void onClick(View v) {
319                    final StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy();
320                    try {
321                        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
322                                               .detectLeakedSqlLiteObjects()
323                                               .penaltyLog()
324                                               .penaltyDropBox()
325                                               .build());
326                        mDb.execSQL("CREATE TABLE IF NOT EXISTS FOO (a INT)");
327                        Cursor c = mDb.rawQuery("SELECT * FROM foo", null);
328                        c = null;  // never close it
329                        Runtime.getRuntime().gc();
330                    } finally {
331                        StrictMode.setVmPolicy(oldPolicy);
332                    }
333
334                }
335            });
336
337        final Button customButton = (Button) findViewById(R.id.custom_button);
338        customButton.setOnClickListener(new View.OnClickListener() {
339                public void onClick(View v) {
340                    StrictMode.noteSlowCall("my example call");
341                }
342            });
343
344        final Button gcInstanceButton = (Button) findViewById(R.id.gc_instance_button);
345        gcInstanceButton.setOnClickListener(new View.OnClickListener() {
346                public void onClick(View v) {
347                    ArrayList<DummyObject> list = new ArrayList<DummyObject>();
348                    list.add(new DummyObject());
349                    list.add(new DummyObject());
350
351                    StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder(StrictMode.getVmPolicy())
352                                           .setClassInstanceLimit(DummyObject.class, 1)
353                                           .penaltyLog()
354                                           .penaltyDropBox()
355                                           .build());
356                    StrictMode.conditionallyCheckInstanceCounts();
357                    list.clear();
358                }
359            });
360
361        final CheckBox checkNoWrite = (CheckBox) findViewById(R.id.policy_no_write);
362        final CheckBox checkNoRead = (CheckBox) findViewById(R.id.policy_no_reads);
363        final CheckBox checkNoNetwork = (CheckBox) findViewById(R.id.policy_no_network);
364        final CheckBox checkCustom = (CheckBox) findViewById(R.id.policy_custom);
365        final CheckBox checkResourceMismatch = (CheckBox) findViewById(R.id.policy_resource_mismatch);
366        final CheckBox checkPenaltyLog = (CheckBox) findViewById(R.id.policy_penalty_log);
367        final CheckBox checkPenaltyDialog = (CheckBox) findViewById(R.id.policy_penalty_dialog);
368        final CheckBox checkPenaltyDeath = (CheckBox) findViewById(R.id.policy_penalty_death);
369        final CheckBox checkPenaltyDropBox = (CheckBox) findViewById(R.id.policy_penalty_dropbox);
370        final CheckBox checkPenaltyFlash = (CheckBox) findViewById(R.id.policy_penalty_flash);
371        final CheckBox checkPenaltyNetworkDeath = (CheckBox) findViewById(R.id.policy_penalty_network_death);
372
373        View.OnClickListener changePolicy = new View.OnClickListener() {
374                public void onClick(View v) {
375                    StrictMode.ThreadPolicy.Builder newPolicy = new StrictMode.ThreadPolicy.Builder();
376                    if (checkNoWrite.isChecked()) newPolicy.detectDiskWrites();
377                    if (checkNoRead.isChecked()) newPolicy.detectDiskReads();
378                    if (checkNoNetwork.isChecked()) newPolicy.detectNetwork();
379                    if (checkCustom.isChecked()) newPolicy.detectCustomSlowCalls();
380                    if (checkResourceMismatch.isChecked()) newPolicy.detectResourceMismatches();
381                    if (checkPenaltyLog.isChecked()) newPolicy.penaltyLog();
382                    if (checkPenaltyDialog.isChecked()) newPolicy.penaltyDialog();
383                    if (checkPenaltyDeath.isChecked()) newPolicy.penaltyDeath();
384                    if (checkPenaltyDropBox.isChecked()) newPolicy.penaltyDropBox();
385                    if (checkPenaltyFlash.isChecked()) newPolicy.penaltyFlashScreen();
386                    if (checkPenaltyNetworkDeath.isChecked()) newPolicy.penaltyDeathOnNetwork();
387                    StrictMode.ThreadPolicy policy = newPolicy.build();
388                    Log.v(TAG, "Changing policy to: " + policy);
389                    StrictMode.setThreadPolicy(policy);
390                }
391            };
392        checkNoWrite.setOnClickListener(changePolicy);
393        checkNoRead.setOnClickListener(changePolicy);
394        checkNoNetwork.setOnClickListener(changePolicy);
395        checkCustom.setOnClickListener(changePolicy);
396        checkResourceMismatch.setOnClickListener(changePolicy);
397        checkPenaltyLog.setOnClickListener(changePolicy);
398        checkPenaltyDialog.setOnClickListener(changePolicy);
399        checkPenaltyDeath.setOnClickListener(changePolicy);
400        checkPenaltyDropBox.setOnClickListener(changePolicy);
401        checkPenaltyFlash.setOnClickListener(changePolicy);
402        checkPenaltyNetworkDeath.setOnClickListener(changePolicy);
403    }
404
405    @Override
406    public void onDestroy() {
407        super.onDestroy();
408        mDb.close();
409        mDb = null;
410    }
411
412    private void closeWithLinger(boolean linger) {
413        Log.d(TAG, "Socket linger test; linger=" + linger);
414        try {
415            Socket socket = new Socket();
416            socket.setSoLinger(linger, 5);
417            socket.close();
418        } catch (IOException e) {
419            Log.e(TAG, "Error with linger close", e);
420        }
421    }
422
423    private void fileReadLoop() {
424        RandomAccessFile raf = null;
425        File filename = getFileStreamPath("test.dat");
426        try {
427            long sumNanos = 0;
428            byte[] buf = new byte[512];
429
430            //raf = new RandomAccessFile(filename, "rw");
431            //raf.write(buf);
432            //raf.close();
433            //raf = null;
434
435            // The data's almost certainly cached -- it's not clear what we're testing here
436            raf = new RandomAccessFile(filename, "r");
437            raf.seek(0);
438            raf.read(buf);
439        } catch (IOException e) {
440            Log.e(TAG, "File read failed", e);
441        } finally {
442            try { if (raf != null) raf.close(); } catch (IOException e) {}
443        }
444    }
445
446    // Returns milliseconds taken, or -1 on failure.
447    private long settingsWrite(int mode) {
448        Cursor c = null;
449        long startTime = SystemClock.uptimeMillis();
450        // The database will take care of replacing duplicates.
451        try {
452            ContentValues values = new ContentValues();
453            values.put("name", "dummy_for_testing");
454            values.put("value", "" + startTime);
455            Uri uri = cr.insert(SYSTEM_SETTINGS_URI, values);
456            Log.v(TAG, "inserted uri: " + uri);
457        } catch (SQLException e) {
458            Log.w(TAG, "sqliteexception during write: " + e);
459            return -1;
460        }
461        long duration = SystemClock.uptimeMillis() - startTime;
462        return duration;
463    }
464
465    @Override public void onResume() {
466        super.onResume();
467        bindService(new Intent(this, LocalService.class),
468                    mLocalServiceConn, Context.BIND_AUTO_CREATE);
469        bindService(new Intent(this, RemoteService.class),
470                    mRemoteServiceConn, Context.BIND_AUTO_CREATE);
471    }
472
473    @Override public void onPause() {
474        super.onPause();
475        unbindService(mLocalServiceConn);
476        unbindService(mRemoteServiceConn);
477    }
478
479    private static class DummyObject {
480        int foo;
481    }
482}
483