Home | History | Annotate | Download | only in rpc_performance
      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 
     17 
     18 // TODO: find AIDL for ContentResolver.  add queryExtensible() returning struct{Cursor?,String? singleRow}
     19 //    -- would have to lazily do real requery(), registerContentObserver(), etc
     20 
     21 package com.android.rpc_performance;
     22 
     23 import android.app.Activity;
     24 import android.content.ComponentName;
     25 import android.content.ContentQueryMap;
     26 import android.content.ContentResolver;
     27 import android.content.ContentValues;
     28 import android.content.IContentProvider;
     29 import android.content.Context;
     30 import android.content.Intent;
     31 import android.content.ServiceConnection;
     32 import android.content.pm.PackageManager;
     33 import android.content.pm.ResolveInfo;
     34 import android.content.res.Configuration;
     35 import android.content.res.Resources;
     36 import android.database.Cursor;
     37 import android.database.SQLException;
     38 import android.net.LocalSocket;
     39 import android.net.LocalSocketAddress;
     40 import android.net.Uri;
     41 import android.os.Binder;
     42 import android.os.Bundle;
     43 import android.os.Debug;
     44 import android.os.Handler;
     45 import android.os.IBinder;
     46 import android.os.Parcel;
     47 import android.os.RemoteException;
     48 import android.os.ServiceManager;
     49 import android.os.SystemClock;
     50 import android.os.StrictMode;
     51 import android.telephony.TelephonyManager;
     52 import android.text.TextUtils;
     53 import android.util.AndroidException;
     54 import android.util.Log;
     55 import android.view.View;
     56 import android.widget.Button;
     57 import android.widget.TextView;
     58 
     59 import java.io.File;
     60 import java.io.FileInputStream;
     61 import java.io.IOException;
     62 import java.io.InputStream;
     63 import java.io.OutputStream;
     64 import java.io.RandomAccessFile;
     65 
     66 public class ProviderPerfActivity extends Activity {
     67 
     68     private static final String TAG = "ProviderPerfActivity";
     69     private static final Uri SYSTEM_SETTINGS_URI = Uri.parse("content://settings/system");
     70 
     71     // No-op provider URLs:
     72     private static final Uri CROSS_PROC_PROVIDER_URI = Uri.parse("content://com.android.rpc_performance/");
     73     private static final Uri IN_PROC_PROVIDER_URI = Uri.parse("content://com.android.rpc_performance.local/");
     74 
     75     private final Handler mHandler = new Handler();
     76     private final static int LOOP_TIME_MILLIS = 2000;
     77     private final static long LOOP_TIME_NANOS = (long) LOOP_TIME_MILLIS * 1000000L;
     78 
     79     private IService mServiceStub = null;
     80     private ServiceConnection serviceConnection = new ServiceConnection() {
     81         public void onServiceConnected(ComponentName name, IBinder service) {
     82             mServiceStub = IService.Stub.asInterface(service);
     83             Log.v(TAG, "Service bound");
     84         }
     85 
     86         public void onServiceDisconnected(ComponentName name) {
     87             mServiceStub = null;
     88             Log.v(TAG, "Service unbound");
     89         };
     90     };
     91 
     92 
     93     private ContentResolver cr;
     94     private int mIterations = 100;
     95     private String mTraceName = null;
     96 
     97     /** Called when the activity is first created. */
     98     @Override
     99     public void onCreate(Bundle savedInstanceState) {
    100         super.onCreate(savedInstanceState);
    101         setContentView(R.layout.main);
    102 
    103         cr = getContentResolver();
    104 
    105         setButtonAction(R.id.file_read_button, new Runnable() {
    106                 public void run() {
    107                     final float avgTime = fileReadLoop();
    108                     endAsyncOp(R.id.file_read_button, R.id.file_read_text, avgTime);
    109                 }});
    110 
    111         setButtonAction(R.id.file_write_button, new Runnable() {
    112                 public void run() {
    113                     final float avgTime = fileWriteLoop();
    114                     endAsyncOp(R.id.file_write_button, R.id.file_write_text, avgTime);
    115                 }});
    116 
    117         setButtonAction(R.id.settings_read_button, new Runnable() {
    118                 public void run() {
    119                     final float avgTime = settingsProviderLoop(MODE_READ, 0);
    120                     endAsyncOp(R.id.settings_read_button, R.id.settings_read_text, avgTime);
    121                 }});
    122 
    123         setButtonAction(R.id.settings_sleep_button, new Runnable() {
    124                 public void run() {
    125                     final float avgTime = settingsProviderLoop(MODE_READ, 100);
    126                     endAsyncOp(R.id.settings_sleep_button, R.id.settings_sleep_text, avgTime);
    127                 }});
    128 
    129         setButtonAction(R.id.settings_write_button, new Runnable() {
    130                 public void run() {
    131                     final float avgTime = settingsProviderLoop(MODE_WRITE, 0);
    132                     endAsyncOp(R.id.settings_write_button, R.id.settings_write_text, avgTime);
    133                 }});
    134 
    135         setButtonAction(R.id.settings_writedup_button, new Runnable() {
    136                 public void run() {
    137                     final float avgTime = settingsProviderLoop(MODE_WRITE_DUP, 0);
    138                     endAsyncOp(R.id.settings_writedup_button, R.id.settings_writedup_text, avgTime);
    139                 }});
    140 
    141         setButtonAction(R.id.dummy_lookup_button, new Runnable() {
    142                 public void run() {
    143                     final float avgTime = noOpProviderLoop(CROSS_PROC_PROVIDER_URI);
    144                     endAsyncOp(R.id.dummy_lookup_button, R.id.dummy_lookup_text, avgTime);
    145                 }});
    146 
    147         setButtonAction(R.id.dummy_local_lookup_button, new Runnable() {
    148                 public void run() {
    149                     final float avgTime = noOpProviderLoop(IN_PROC_PROVIDER_URI);
    150                     endAsyncOp(R.id.dummy_local_lookup_button,
    151                                R.id.dummy_local_lookup_text, avgTime);
    152                 }});
    153 
    154         setButtonAction(R.id.localsocket_button, new Runnable() {
    155                 public void run() {
    156                     final float avgTime = localSocketLoop();
    157                     endAsyncOp(R.id.localsocket_button, R.id.localsocket_text, avgTime);
    158                 }});
    159 
    160         setButtonAction(R.id.service_button, new Runnable() {
    161                 public void run() {
    162                     final float avgTime = serviceLoop(null);
    163                     endAsyncOp(R.id.service_button, R.id.service_text, avgTime);
    164                 }});
    165 
    166         setButtonAction(R.id.service2_button, new Runnable() {
    167                 public void run() {
    168                     final float avgTime = serviceLoop("xyzzy");
    169                     endAsyncOp(R.id.service2_button, R.id.service2_text, avgTime);
    170                 }});
    171 
    172         setButtonAction(R.id.ping_media_button, new Runnable() {
    173                 public void run() {
    174                     final float avgTime = pingServiceLoop("media.player");
    175                     endAsyncOp(R.id.ping_media_button, R.id.ping_media_text, avgTime);
    176                 }});
    177 
    178         setButtonAction(R.id.ping_activity_button, new Runnable() {
    179                 public void run() {
    180                     final float avgTime = pingServiceLoop("activity");
    181                     endAsyncOp(R.id.ping_activity_button, R.id.ping_activity_text, avgTime);
    182                 }});
    183 
    184         setButtonAction(R.id.proc_button, new Runnable() {
    185                 public void run() {
    186                     final float avgTime = procLoop();
    187                     endAsyncOp(R.id.proc_button, R.id.proc_text, avgTime);
    188                 }});
    189 
    190         setButtonAction(R.id.call_button, new Runnable() {
    191                 public void run() {
    192                     final float avgTime = callLoop("ringtone");
    193                     endAsyncOp(R.id.call_button, R.id.call_text, avgTime);
    194                 }});
    195 
    196         setButtonAction(R.id.call2_button, new Runnable() {
    197                 public void run() {
    198                     final float avgTime = callLoop("XXXXXXXX");  // non-existent
    199                     endAsyncOp(R.id.call2_button, R.id.call2_text, avgTime);
    200                 }});
    201 
    202         setButtonAction(R.id.obtain_button, new Runnable() {
    203                 public void run() {
    204                     final float avgTime = parcelLoop(true);
    205                     endAsyncOp(R.id.obtain_button, R.id.obtain_text, avgTime);
    206                 }});
    207 
    208         setButtonAction(R.id.recycle_button, new Runnable() {
    209                 public void run() {
    210                     final float avgTime = parcelLoop(false);
    211                     endAsyncOp(R.id.recycle_button, R.id.recycle_text, avgTime);
    212                 }});
    213 
    214         setButtonAction(R.id.strictmode_button, new Runnable() {
    215                 public void run() {
    216                     final float avgTime = strictModeLoop(true);
    217                     endAsyncOp(R.id.strictmode_button, R.id.strictmode_text, avgTime);
    218                 }});
    219 
    220         setButtonAction(R.id.binderstrict_button, new Runnable() {
    221                 public void run() {
    222                     final float avgTime = strictModeLoop(false);
    223                     endAsyncOp(R.id.binderstrict_button, R.id.binderstrict_text, avgTime);
    224                 }});
    225     }
    226 
    227     @Override public void onResume() {
    228         super.onResume();
    229 
    230         bindService(new Intent(this, MiscService.class),
    231                     serviceConnection, Context.BIND_AUTO_CREATE);
    232     }
    233 
    234     @Override public void onPause() {
    235         super.onPause();
    236         if (serviceConnection != null) {
    237             unbindService(serviceConnection);
    238         }
    239     }
    240 
    241     private void setButtonAction(int button_id, final Runnable r) {
    242         final Button button = (Button) findViewById(button_id);
    243         button.setOnClickListener(new View.OnClickListener() {
    244             public void onClick(View v) {
    245                 button.requestFocus();
    246                 button.setEnabled(false);
    247 
    248                 TextView tvIter = (TextView) findViewById(R.id.iterations_edit);
    249                 try {
    250                     mIterations = Integer.parseInt(tvIter.getText().toString());
    251                 } catch (NumberFormatException e) {
    252                     Log.w(TAG, "Invalid iteration count", e);
    253                     if (tvIter != null) tvIter.setText(Integer.toString(mIterations));
    254                 }
    255 
    256                 TextView tvTrace = (TextView) findViewById(R.id.trace_edit);
    257                 String name = tvTrace.getText().toString();
    258                 if (name != null && name.length() > 0) {
    259                     mTraceName = name;
    260                     Debug.startMethodTracing(name);
    261                 }
    262 
    263                 new Thread(r).start();
    264             }
    265         });
    266     }
    267 
    268     private void endAsyncOp(final int button_id, final int text_id, final float avgTime) {
    269         mHandler.post(new Runnable() {
    270             public void run() {
    271                 Debug.stopMethodTracing();
    272                 findViewById(button_id).setEnabled(true);
    273                 setTextTime(text_id, avgTime);
    274             }
    275         });
    276     }
    277 
    278     private void setTextTime(int id, float avgTime) {
    279         TextView tv = (TextView) findViewById(id);
    280         if (tv == null) return;
    281         String text = tv.getText().toString();
    282         text = text.substring(0, text.indexOf(':') + 1) + "\n" + avgTime + " ms avg";
    283         tv.setText(text);
    284     }
    285 
    286     private float fileReadLoop() {
    287         RandomAccessFile raf = null;
    288         File filename = getFileStreamPath("test.dat");
    289         try {
    290             long sumNanos = 0;
    291             byte[] buf = new byte[512];
    292 
    293             raf = new RandomAccessFile(filename, "rw");
    294             raf.write(buf);
    295             raf.close();
    296             raf = null;
    297 
    298             // The data's almost certainly cached -- it's not clear what we're testing here
    299             raf = new RandomAccessFile(filename, "r");
    300             for (int i = 0; i < mIterations; i++) {
    301                 long lastTime = System.nanoTime();
    302                 raf.seek(0);
    303                 raf.read(buf);
    304                 sumNanos += System.nanoTime() - lastTime;
    305             }
    306 
    307             return (float) sumNanos / Math.max(1.0f, (float) mIterations) / 1000000.0f;
    308         } catch (IOException e) {
    309             Log.e(TAG, "File read failed", e);
    310             return 0;
    311         } finally {
    312             try { if (raf != null) raf.close(); } catch (IOException e) {}
    313         }
    314     }
    315 
    316     private float fileWriteLoop() {
    317         RandomAccessFile raf = null;
    318         File filename = getFileStreamPath("test.dat");
    319         try {
    320             long sumNanos = 0;
    321             byte[] buf = new byte[512];
    322             for (int i = 0; i < mIterations; i++) {
    323                 for (int j = 0; j < buf.length; j++) buf[j] = (byte) (i + j);
    324                 long lastTime = System.nanoTime();
    325                 raf = new RandomAccessFile(filename, "rw");
    326                 raf.write(buf);
    327                 raf.close();
    328                 raf = null;
    329                 sumNanos += System.nanoTime() - lastTime;
    330             }
    331 
    332             return (float) sumNanos / Math.max(1.0f, (float) mIterations) / 1000000.0f;
    333         } catch (IOException e) {
    334             Log.e(TAG, "File read failed", e);
    335             return 0;
    336         } finally {
    337             try { if (raf != null) raf.close(); } catch (IOException e) {}
    338         }
    339     }
    340 
    341     // Returns average cross-process dummy query time in milliseconds.
    342     private float noOpProviderLoop(Uri uri) {
    343         long sumNanos = 0;
    344         int failures = 0;
    345         int total = 0;
    346         for (int i = 0; i < mIterations; i++) {
    347             long duration = doNoOpLookup(uri);
    348             if (duration < 0) {
    349                 failures++;
    350             } else {
    351                 total++;
    352                 sumNanos += duration;
    353             }
    354         }
    355         float averageMillis = (float) sumNanos /
    356             (float) (total != 0 ? total : 1) /
    357             1000000.0f;
    358         Log.v(TAG, "dummy loop: fails=" + failures + "; total=" + total + "; goodavg ms=" + averageMillis);
    359         return averageMillis;
    360     }
    361 
    362     // Returns average cross-process dummy query time in milliseconds.
    363     private float callLoop(String key) {
    364         IContentProvider cp = cr.acquireProvider(SYSTEM_SETTINGS_URI.getAuthority());
    365 
    366         long sumNanos = 0;
    367         int total = 0;
    368 
    369         try {
    370             for (int i = 0; i < mIterations; i++) {
    371                 long lastTime = System.nanoTime();
    372                 Bundle b = cp.call("GET_system", key, null);
    373                 long nowTime = System.nanoTime();
    374                 total++;
    375                 sumNanos += (nowTime - lastTime);
    376             }
    377         } catch (RemoteException e) {
    378             return -999.0f;
    379         }
    380         float averageMillis = (float) sumNanos /
    381             (float) (total != 0 ? total : 1) /
    382             1000000.0f;
    383         Log.v(TAG, "call loop: avg_ms=" + averageMillis + "; calls=" + total);
    384         return averageMillis;
    385     }
    386 
    387     // Returns average time to read a /proc file in milliseconds.
    388     private float procLoop() {
    389         long sumNanos = 0;
    390         int total = 0;
    391         File f = new File("/proc/self/cmdline");
    392         byte[] buf = new byte[100];
    393         String value = null;
    394         try {
    395             for (int i = 0; i < mIterations; i++) {
    396                 long lastTime = System.nanoTime();
    397                 FileInputStream is = new FileInputStream(f);
    398                 int readBytes = is.read(buf, 0, 100);
    399                 is.close();
    400                 //value = new String(buf, 0, readBytes);
    401                 long nowTime = System.nanoTime();
    402                 total++;
    403                 sumNanos += (nowTime - lastTime);
    404                 lastTime = nowTime;
    405             }
    406         } catch (IOException e) {
    407             return -999.0f;
    408         }
    409         float averageMillis = (float) sumNanos /
    410             (float) (total != 0 ? total : 1) /
    411             1000000.0f;
    412         Log.v(TAG, "proc loop: total: " + total + "; avg_ms=" + averageMillis + "; value=" + value);
    413         return averageMillis;
    414     }
    415 
    416     private static final String[] IGNORED_COLUMN = {"ignored"};
    417 
    418     // Returns nanoseconds.
    419     private long doNoOpLookup(Uri uri) {
    420         Cursor c = null;
    421         try {
    422             long startTime = System.nanoTime();
    423             c = cr.query(uri,
    424                          IGNORED_COLUMN,  //new String[]{"ignored"},  // but allocate it for apples-to-apples
    425                          "name=?",
    426                          IGNORED_COLUMN,  // new String[]{"also_ignored"},  // also for equality in benchmarking
    427                          null /* sort order */);
    428             if (c == null) {
    429                 Log.w(TAG, "cursor null");
    430                 return -1;
    431             }
    432             String value = c.moveToNext() ? c.getString(0) : null;
    433             long duration = System.nanoTime() - startTime;
    434             //Log.v(TAG, "got value: " + value + " in " + duration);
    435             return duration;
    436         } catch (SQLException e) {
    437             Log.w(TAG, "sqlite exception: " + e);
    438             return -1;
    439         } finally {
    440             if (c != null) c.close();
    441         }
    442     }
    443 
    444     // Returns average cross-process dummy query time in milliseconds.
    445     private float serviceLoop(String value) {
    446         if (mServiceStub == null) {
    447             Log.v(TAG, "No service stub.");
    448             return -999;
    449         }
    450 
    451         String dummy = null;
    452         try {
    453             if (mTraceName != null) mServiceStub.startTracing(mTraceName + ".service");
    454 
    455             long sumNanos = 0;
    456             for (int i = 0; i < mIterations; i++) {
    457                 long lastTime = System.nanoTime();
    458                 if (value == null) {
    459                     mServiceStub.pingVoid();
    460                 } else {
    461                     value = mServiceStub.pingString(value);
    462                 }
    463                 sumNanos += System.nanoTime() - lastTime;
    464             }
    465 
    466             if (mTraceName != null) mServiceStub.stopTracing();
    467 
    468             return (float) sumNanos / Math.max(1.0f, (float) mIterations) / 1000000.0f;
    469         } catch (RemoteException e) {
    470             Log.e(TAG, "Binder call failed", e);
    471             return -999;
    472         }
    473     }
    474 
    475     // Returns average cross-process binder ping time in milliseconds.
    476     private float pingServiceLoop(String service) {
    477         IBinder binder = ServiceManager.getService(service);
    478         if (binder == null) {
    479             Log.e(TAG, "Service missing: " + service);
    480             return -1.0f;
    481         }
    482 
    483         long sumNanos = 0;
    484         for (int i = 0; i < mIterations; i++) {
    485             long lastTime = System.nanoTime();
    486             if (!binder.pingBinder()) {
    487                 Log.e(TAG, "Error pinging service: " + service);
    488                 return -1.0f;
    489             }
    490             sumNanos += System.nanoTime() - lastTime;
    491         }
    492 
    493         return (float) sumNanos / Math.max(1.0f, (float) mIterations) / 1000000.0f;
    494     }
    495 
    496     // Returns average milliseconds.
    497     private float localSocketLoop() {
    498         LocalSocket socket = null;
    499         try {
    500             socket = new LocalSocket();
    501             Log.v(TAG, "Connecting to socket...");
    502             socket.connect(new LocalSocketAddress(MiscService.SOCKET_NAME));
    503             Log.v(TAG, "Connected to socket.");
    504             InputStream is = socket.getInputStream();
    505             OutputStream os = socket.getOutputStream();
    506 
    507             int count = 0;
    508             long sumNanos = 0;
    509             for (int i = 0; i < mIterations; i++) {
    510                 long beforeTime = System.nanoTime();
    511                 int expectByte = count & 0xff;
    512                 os.write(expectByte);
    513                 int gotBackByte = is.read();
    514 
    515                 long afterTime = System.nanoTime();
    516                 sumNanos += (afterTime - beforeTime);
    517 
    518                 if (gotBackByte != expectByte) {
    519                     Log.w(TAG, "Got wrong byte back.  Got: " + gotBackByte
    520                           + "; wanted=" + expectByte);
    521                     return -999.00f;
    522                 }
    523                 count++;
    524             }
    525             return count == 0 ? 0.0f : ((float) sumNanos / (float) count / 1000000.0f);
    526         } catch (IOException e) {
    527             Log.v(TAG, "error in localSocketLoop: " + e);
    528             return -1.0f;
    529         } finally {
    530             if (socket != null) {
    531                 try { socket.close(); } catch (IOException e) {}
    532             }
    533         }
    534     }
    535 
    536     // Returns average operation time in milliseconds.
    537     // obtain: true = measure obtain(), false = measure recycle()
    538     private float parcelLoop(boolean obtain) {
    539         long sumNanos = 0;
    540         for (int i = 0; i < mIterations; i++) {
    541             if (obtain) {
    542                 long lastTime = System.nanoTime();
    543                 Parcel p = Parcel.obtain();
    544                 sumNanos += System.nanoTime() - lastTime;
    545                 p.recycle();
    546             } else {
    547                 Parcel p = Parcel.obtain();
    548                 long lastTime = System.nanoTime();
    549                 p.recycle();
    550                 sumNanos += System.nanoTime() - lastTime;
    551             }
    552         }
    553 
    554         return (float) sumNanos / Math.max(1.0f, (float) mIterations) / 1000000.0f;
    555     }
    556 
    557     private float strictModeLoop(boolean full) {
    558         StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
    559         int oldPolicyMask = StrictMode.getThreadPolicyMask();  // hidden API
    560         long sumNanos = 0;
    561         StrictMode.ThreadPolicy policyA =
    562                 new StrictMode.ThreadPolicy.Builder().detectDiskReads().build();
    563         StrictMode.ThreadPolicy policyB =
    564                 new StrictMode.ThreadPolicy.Builder().detectDiskWrites().build();
    565         for (int i = 0; i < mIterations; i++) {
    566             StrictMode.ThreadPolicy policy = ((i & 1) == 1) ? policyA : policyB;
    567             int policyMask = ((i & 1) == 1) ? 1 : 2;
    568             if (full) {
    569                 long lastTime = System.nanoTime();
    570                 StrictMode.setThreadPolicy(policy);
    571                 sumNanos += System.nanoTime() - lastTime;
    572             } else {
    573                 long lastTime = System.nanoTime();
    574                 Binder.setThreadStrictModePolicy(policyMask);
    575                 sumNanos += System.nanoTime() - lastTime;
    576             }
    577         }
    578         if (full) {
    579             StrictMode.setThreadPolicy(oldPolicy);
    580         } else {
    581             Binder.setThreadStrictModePolicy(oldPolicyMask);
    582         }
    583         return (float) sumNanos / Math.max(1.0f, (float) mIterations) / 1000000.0f;
    584     }
    585 
    586     // Returns average milliseconds.
    587     private static final int MODE_READ = 0;
    588     private static final int MODE_WRITE = 1;
    589     private static final int MODE_WRITE_DUP = 2;
    590 
    591     private float settingsProviderLoop(int mode, long innerSleep) {
    592         long sumMillis = 0;
    593         int total = 0;
    594         for (int i = 0; i < mIterations; i++) {
    595             long duration = mode == MODE_READ ? settingsRead(innerSleep) : settingsWrite(mode);
    596             if (duration < 0) {
    597                 return -999.0f;
    598             }
    599             total++;
    600             sumMillis += duration;
    601         }
    602         float averageMillis = ((float) sumMillis / (float) (total != 0 ? total : 1));
    603         Log.v(TAG, "settings provider; mode=" + mode + "; total=" + total +
    604               "; goodavg_ms=" + averageMillis);
    605         return averageMillis;
    606     }
    607 
    608     // Returns milliseconds taken, or -1 on failure.
    609     private long settingsRead(long innerSleep) {
    610         Cursor c = null;
    611         try {
    612             long startTime = SystemClock.uptimeMillis();
    613             c = cr.query(SYSTEM_SETTINGS_URI,
    614                          new String[]{"value"},
    615                          "name=?",
    616                          new String[]{"airplane_mode_on"},
    617                          null /* sort order */);
    618             if (c == null) {
    619                 Log.w(TAG, "cursor null");
    620                 return -1;
    621             }
    622             String value = c.moveToNext() ? c.getString(0) : null;
    623             long duration = SystemClock.uptimeMillis() - startTime;
    624             if (innerSleep > 0) {
    625                 try {
    626                     Thread.sleep(innerSleep);
    627                 } catch (InterruptedException e) {}
    628             }
    629             return duration;
    630         } catch (SQLException e) {
    631             Log.w(TAG, "sqlite exception: " + e);
    632             return -1;
    633         } finally {
    634             if (c != null) c.close();
    635         }
    636     }
    637 
    638     // Returns milliseconds taken, or -1 on failure.
    639     private long settingsWrite(int mode) {
    640         Cursor c = null;
    641         long startTime = SystemClock.uptimeMillis();
    642         // The database will take care of replacing duplicates.
    643         try {
    644             ContentValues values = new ContentValues();
    645             values.put("name", "dummy_for_testing");
    646             values.put("value", (mode == MODE_WRITE ? (""+startTime) : "foo"));
    647             Uri uri = cr.insert(SYSTEM_SETTINGS_URI, values);
    648             Log.v(TAG, "inserted uri: " + uri);
    649         } catch (SQLException e) {
    650             Log.w(TAG, "sqliteexception during write: " + e);
    651             return -1;
    652         }
    653         long duration = SystemClock.uptimeMillis() - startTime;
    654         return duration;
    655     }
    656 }
    657