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
onCreate(Bundle savedInstanceState)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 
onResume()227     @Override public void onResume() {
228         super.onResume();
229 
230         bindService(new Intent(this, MiscService.class),
231                     serviceConnection, Context.BIND_AUTO_CREATE);
232     }
233 
onPause()234     @Override public void onPause() {
235         super.onPause();
236         if (serviceConnection != null) {
237             unbindService(serviceConnection);
238         }
239     }
240 
setButtonAction(int button_id, final Runnable r)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 
endAsyncOp(final int button_id, final int text_id, final float avgTime)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 
setTextTime(int id, float avgTime)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 
fileReadLoop()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 
fileWriteLoop()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.
noOpProviderLoop(Uri uri)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.
callLoop(String key)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.
procLoop()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.
doNoOpLookup(Uri uri)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.
serviceLoop(String value)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.
pingServiceLoop(String service)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.
localSocketLoop()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()
parcelLoop(boolean obtain)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 
strictModeLoop(boolean full)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 
settingsProviderLoop(int mode, long innerSleep)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.
settingsRead(long innerSleep)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.
settingsWrite(int mode)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