Integrated the profiler into the framework. We run it all the time if the persist.sampling_profiler
system property is set. Saves snapshots to the SD card.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 8a26aba..2877aec 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -68,6 +68,7 @@
 
 import com.android.internal.os.BinderInternal;
 import com.android.internal.os.RuntimeInit;
+import com.android.internal.os.SamplingProfilerIntegration;
 import com.android.internal.util.ArrayUtils;
 
 import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
@@ -87,6 +88,8 @@
 import java.util.TimeZone;
 import java.util.regex.Pattern;
 
+import dalvik.system.SamplingProfiler;
+
 final class IntentReceiverLeaked extends AndroidRuntimeException {
     public IntentReceiverLeaked(String msg) {
         super(msg);
@@ -126,7 +129,7 @@
     private static final int LOG_ON_PAUSE_CALLED = 30021;
     private static final int LOG_ON_RESUME_CALLED = 30022;
 
-    
+
     public static final ActivityThread currentActivityThread() {
         return (ActivityThread)sThreadLocal.get();
     }
@@ -314,7 +317,7 @@
         public ApplicationInfo getApplicationInfo() {
             return mApplicationInfo;
         }
-        
+
         public boolean isSecurityViolation() {
             return mSecurityViolation;
         }
@@ -322,7 +325,7 @@
         /**
          * Gets the array of shared libraries that are listed as
          * used by the given package.
-         * 
+         *
          * @param packageName the name of the package (note: not its
          * file name)
          * @return null-ok; the array of shared libraries, each one
@@ -350,7 +353,7 @@
          * result is a single string with the names of the libraries
          * separated by colons, or <code>null</code> if both lists
          * were <code>null</code> or empty.
-         * 
+         *
          * @param list1 null-ok; the first list
          * @param list2 null-ok; the second list
          * @return null-ok; the combination
@@ -378,7 +381,7 @@
                     if (dupCheck && ArrayUtils.contains(list1, s)) {
                         continue;
                     }
-                    
+
                     if (first) {
                         first = false;
                     } else {
@@ -390,7 +393,7 @@
 
             return result.toString();
         }
-                
+
         public ClassLoader getClassLoader() {
             synchronized (this) {
                 if (mClassLoader != null) {
@@ -428,7 +431,7 @@
 
                     if ((mSharedLibraries != null) ||
                             (instrumentationLibs != null)) {
-                        zip = 
+                        zip =
                             combineLibs(mSharedLibraries, instrumentationLibs)
                             + ':' + zip;
                     }
@@ -485,9 +488,9 @@
             if (mApplication != null) {
                 return mApplication;
             }
-            
+
             Application app = null;
-            
+
             String appClass = mApplicationInfo.className;
             if (forceDefaultAppClass || (appClass == null)) {
                 appClass = "android.app.Application";
@@ -510,7 +513,7 @@
             mActivityThread.mAllApplications.add(app);
             return mApplication = app;
         }
-        
+
         public void removeContextRegistrations(Context context,
                 String who, String what) {
             HashMap<BroadcastReceiver, ReceiverDispatcher> rmap =
@@ -643,7 +646,7 @@
             final static class InnerReceiver extends IIntentReceiver.Stub {
                 final WeakReference<ReceiverDispatcher> mDispatcher;
                 final ReceiverDispatcher mStrongRef;
-                
+
                 InnerReceiver(ReceiverDispatcher rd, boolean strong) {
                     mDispatcher = new WeakReference<ReceiverDispatcher>(rd);
                     mStrongRef = strong ? rd : null;
@@ -661,7 +664,7 @@
                     }
                 }
             }
-            
+
             final IIntentReceiver.Stub mIIntentReceiver;
             final BroadcastReceiver mReceiver;
             final Context mContext;
@@ -770,7 +773,7 @@
             BroadcastReceiver getIntentReceiver() {
                 return mReceiver;
             }
-            
+
             IIntentReceiver getIIntentReceiver() {
                 return mIIntentReceiver;
             }
@@ -901,7 +904,7 @@
 
             private static class InnerConnection extends IServiceConnection.Stub {
                 final WeakReference<ServiceDispatcher> mDispatcher;
-                
+
                 InnerConnection(ServiceDispatcher sd) {
                     mDispatcher = new WeakReference<ServiceDispatcher>(sd);
                 }
@@ -913,7 +916,7 @@
                     }
                 }
             }
-            
+
             private final HashMap<ComponentName, ConnectionInfo> mActiveConnections
                 = new HashMap<ComponentName, ConnectionInfo>();
 
@@ -965,7 +968,7 @@
             IServiceConnection getIServiceConnection() {
                 return mIServiceConnection;
             }
-            
+
             int getFlags() {
                 return mFlags;
             }
@@ -1191,7 +1194,7 @@
                     + " mode=" + backupMode + "}";
         }
     }
-    
+
     private static final class CreateServiceData {
         IBinder token;
         ServiceInfo info;
@@ -1271,10 +1274,10 @@
         private static final String HEAP_COLUMN = "%17s %8s %8s %8s %8s";
         private static final String ONE_COUNT_COLUMN = "%17s %8d";
         private static final String TWO_COUNT_COLUMNS = "%17s %8d %17s %8d";
-        
+
         // Formatting for checkin service - update version if row format changes
         private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 1;
-       
+
         public final void schedulePauseActivity(IBinder token, boolean finished,
                 boolean userLeaving, int configChanges) {
             queueOrSendMessage(
@@ -1343,7 +1346,7 @@
             synchronized (mRelaunchingActivities) {
                 mRelaunchingActivities.add(r);
             }
-            
+
             queueOrSendMessage(H.RELAUNCH_ACTIVITY, r, configChanges);
         }
 
@@ -1514,7 +1517,7 @@
                 throws RemoteException {
             receiver.performReceive(intent, resultCode, dataStr, extras, ordered);
         }
-        
+
         public void scheduleLowMemory() {
             queueOrSendMessage(H.LOW_MEMORY, null);
         }
@@ -1530,7 +1533,7 @@
             } catch (RemoteException e) {
             }
         }
-        
+
         public void profilerControl(boolean start, String path, ParcelFileDescriptor fd) {
             ProfilerControlData pcd = new ProfilerControlData();
             pcd.path = path;
@@ -1549,11 +1552,11 @@
                 Log.w(TAG, "Failed setting process group to " + group, e);
             }
         }
-        
+
         public void getMemoryInfo(Debug.MemoryInfo outInfo) {
             Debug.getMemoryInfo(outInfo);
         }
-        
+
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             long nativeMax = Debug.getNativeHeapSize() / 1024;
@@ -1589,7 +1592,7 @@
             long sqliteAllocated = SQLiteDebug.getHeapAllocatedSize() / 1024;
             SQLiteDebug.PagerStats stats = new SQLiteDebug.PagerStats();
             SQLiteDebug.getPagerStats(stats);
-            
+
             // Check to see if we were called by checkin server. If so, print terse format.
             boolean doCheckinFormat = false;
             if (args != null) {
@@ -1597,79 +1600,79 @@
                     if ("-c".equals(arg)) doCheckinFormat = true;
                 }
             }
-            
+
             // For checkin, we print one long comma-separated list of values
             if (doCheckinFormat) {
                 // NOTE: if you change anything significant below, also consider changing
                 // ACTIVITY_THREAD_CHECKIN_VERSION.
-                String processName = (mBoundApplication != null) 
+                String processName = (mBoundApplication != null)
                         ? mBoundApplication.processName : "unknown";
-                
+
                 // Header
                 pw.print(ACTIVITY_THREAD_CHECKIN_VERSION); pw.print(',');
                 pw.print(Process.myPid()); pw.print(',');
                 pw.print(processName); pw.print(',');
-                
+
                 // Heap info - max
                 pw.print(nativeMax); pw.print(',');
                 pw.print(dalvikMax); pw.print(',');
                 pw.print("N/A,");
                 pw.print(nativeMax + dalvikMax); pw.print(',');
-                
+
                 // Heap info - allocated
                 pw.print(nativeAllocated); pw.print(',');
                 pw.print(dalvikAllocated); pw.print(',');
                 pw.print("N/A,");
                 pw.print(nativeAllocated + dalvikAllocated); pw.print(',');
-                
+
                 // Heap info - free
                 pw.print(nativeFree); pw.print(',');
                 pw.print(dalvikFree); pw.print(',');
                 pw.print("N/A,");
                 pw.print(nativeFree + dalvikFree); pw.print(',');
-                
+
                 // Heap info - proportional set size
                 pw.print(memInfo.nativePss); pw.print(',');
                 pw.print(memInfo.dalvikPss); pw.print(',');
                 pw.print(memInfo.otherPss); pw.print(',');
                 pw.print(memInfo.nativePss + memInfo.dalvikPss + memInfo.otherPss); pw.print(',');
-                
+
                 // Heap info - shared
-                pw.print(nativeShared); pw.print(','); 
-                pw.print(dalvikShared); pw.print(','); 
-                pw.print(otherShared); pw.print(','); 
+                pw.print(nativeShared); pw.print(',');
+                pw.print(dalvikShared); pw.print(',');
+                pw.print(otherShared); pw.print(',');
                 pw.print(nativeShared + dalvikShared + otherShared); pw.print(',');
-                
+
                 // Heap info - private
-                pw.print(nativePrivate); pw.print(','); 
+                pw.print(nativePrivate); pw.print(',');
                 pw.print(dalvikPrivate); pw.print(',');
                 pw.print(otherPrivate); pw.print(',');
                 pw.print(nativePrivate + dalvikPrivate + otherPrivate); pw.print(',');
-                
+
                 // Object counts
                 pw.print(viewInstanceCount); pw.print(',');
                 pw.print(viewRootInstanceCount); pw.print(',');
                 pw.print(appContextInstanceCount); pw.print(',');
                 pw.print(activityInstanceCount); pw.print(',');
-                
+
                 pw.print(globalAssetCount); pw.print(',');
                 pw.print(globalAssetManagerCount); pw.print(',');
                 pw.print(binderLocalObjectCount); pw.print(',');
                 pw.print(binderProxyObjectCount); pw.print(',');
-                
+
                 pw.print(binderDeathObjectCount); pw.print(',');
                 pw.print(openSslSocketCount); pw.print(',');
-                
+
                 // SQL
                 pw.print(sqliteAllocated); pw.print(',');
-                pw.print(stats.databaseBytes / 1024); pw.print(','); 
+                pw.print(stats.databaseBytes / 1024); pw.print(',');
                 pw.print(stats.numPagers); pw.print(',');
                 pw.print((stats.totalBytes - stats.referencedBytes) / 1024); pw.print(',');
                 pw.print(stats.referencedBytes / 1024); pw.print('\n');
-                
+
                 return;
             }
-            
+
             // otherwise, show human-readable format
             printRow(pw, HEAP_COLUMN, "", "native", "dalvik", "other", "total");
             printRow(pw, HEAP_COLUMN, "size:", nativeMax, dalvikMax, "N/A", nativeMax + dalvikMax);
@@ -1702,7 +1705,7 @@
             printRow(pw, ONE_COUNT_COLUMN, "Death Recipients:", binderDeathObjectCount);
 
             printRow(pw, ONE_COUNT_COLUMN, "OpenSSL Sockets:", openSslSocketCount);
-            
+
             // SQLite mem info
             pw.println(" ");
             pw.println(" SQL");
@@ -1711,7 +1714,7 @@
             printRow(pw, TWO_COUNT_COLUMNS, "numPagers:", stats.numPagers, "inactivePageKB:",
                     (stats.totalBytes - stats.referencedBytes) / 1024);
             printRow(pw, ONE_COUNT_COLUMN, "activePageKB:", stats.referencedBytes / 1024);
-            
+
             // Asset details.
             String assetAlloc = AssetManager.getAssetAllocations();
             if (assetAlloc != null) {
@@ -1727,6 +1730,10 @@
     }
 
     private final class H extends Handler {
+        private H() {
+            SamplingProfiler.getInstance().setEventThread(mLooper.getThread());
+        }
+
         public static final int LAUNCH_ACTIVITY         = 100;
         public static final int PAUSE_ACTIVITY          = 101;
         public static final int PAUSE_ACTIVITY_FINISHING= 102;
@@ -1811,6 +1818,7 @@
                 } break;
                 case PAUSE_ACTIVITY:
                     handlePauseActivity((IBinder)msg.obj, false, msg.arg1 != 0, msg.arg2);
+                    maybeSnapshot();
                     break;
                 case PAUSE_ACTIVITY_FINISHING:
                     handlePauseActivity((IBinder)msg.obj, true, msg.arg1 != 0, msg.arg2);
@@ -1853,6 +1861,7 @@
                     break;
                 case RECEIVER:
                     handleReceiver((ReceiverData)msg.obj);
+                    maybeSnapshot();
                     break;
                 case CREATE_SERVICE:
                     handleCreateService((CreateServiceData)msg.obj);
@@ -1868,6 +1877,7 @@
                     break;
                 case STOP_SERVICE:
                     handleStopService((IBinder)msg.obj);
+                    maybeSnapshot();
                     break;
                 case REQUEST_THUMBNAIL:
                     handleRequestThumbnail((IBinder)msg.obj);
@@ -1907,6 +1917,13 @@
                     break;
             }
         }
+
+        void maybeSnapshot() {
+            if (mBoundApplication != null) {
+                SamplingProfilerIntegration.writeSnapshot(
+                        mBoundApplication.processName);
+            }
+        }
     }
 
     private final class Idler implements MessageQueue.IdleHandler {
@@ -1947,13 +1964,13 @@
         final private String mResDir;
         final private float mScale;
         final private int mHash;
-        
+
         ResourcesKey(String resDir, float scale) {
             mResDir = resDir;
             mScale = scale;
             mHash = mResDir.hashCode() << 2 + (int) (mScale * 2);
         }
-        
+
         @Override
         public int hashCode() {
             return mHash;
@@ -2004,7 +2021,7 @@
     final ArrayList<ActivityRecord> mRelaunchingActivities
             = new ArrayList<ActivityRecord>();
     Configuration mPendingConfiguration = null;
-    
+
     // These can be accessed by multiple threads; mPackages is the lock.
     // XXX For now we keep around information about all packages we have
     // seen, not removing entries from this map.
@@ -2139,7 +2156,7 @@
             return false;
         }
     }
-    
+
     ActivityThread() {
     }
 
@@ -2172,11 +2189,11 @@
     public Application getApplication() {
         return mInitialApplication;
     }
-    
+
     public String getProcessName() {
         return mBoundApplication.processName;
     }
-    
+
     public ApplicationContext getSystemContext() {
         synchronized (this) {
             if (mSystemContext == null) {
@@ -2231,7 +2248,7 @@
         }
         return aInfo;
     }
-    
+
     public final Activity startActivityNow(Activity parent, String id,
         Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state,
         Object lastNonConfigurationInstance) {
@@ -2314,7 +2331,7 @@
             r.packageInfo = getPackageInfo(aInfo.applicationInfo,
                     Context.CONTEXT_INCLUDE_CODE);
         }
-            
+
         ComponentName component = r.intent.getComponent();
         if (component == null) {
             component = r.intent.resolveActivity(
@@ -2346,7 +2363,7 @@
 
         try {
             Application app = r.packageInfo.makeApplication(false);
-            
+
             if (localLOGV) Log.v(TAG, "Performing launch of " + r);
             if (localLOGV) Log.v(
                     TAG, r + ": app=" + app
@@ -2365,7 +2382,7 @@
                         r.ident, app, r.intent, r.activityInfo, title, r.parent,
                         r.embeddedID, r.lastNonConfigurationInstance,
                         r.lastNonConfigurationChildInstances, config);
-                
+
                 if (customIntent != null) {
                     activity.mIntent = customIntent;
                 }
@@ -2503,7 +2520,7 @@
             }
         }
     }
-    
+
     private final void handleNewIntent(NewIntentData data) {
         performNewIntents(data.token, data.intents);
     }
@@ -2541,7 +2558,7 @@
 
         try {
             Application app = packageInfo.makeApplication(false);
-            
+
             if (localLOGV) Log.v(
                 TAG, "Performing receive of " + data.intent
                 + ": app=" + app
@@ -2598,7 +2615,7 @@
                     + " already exists");
             return;
         }
-        
+
         BackupAgent agent = null;
         String classname = data.appInfo.backupAgentName;
         if (classname == null) {
@@ -2652,7 +2669,7 @@
     // Tear down a BackupAgent
     private final void handleDestroyBackupAgent(CreateBackupAgentData data) {
         if (DEBUG_BACKUP) Log.v(TAG, "handleDestroyBackupAgent: " + data);
-        
+
         PackageInfo packageInfo = getPackageInfoNoCheck(data.appInfo);
         String packageName = packageInfo.mPackageName;
         BackupAgent agent = mBackupAgents.get(packageName);
@@ -2857,9 +2874,9 @@
                 }
                 r.activity.performResume();
 
-                EventLog.writeEvent(LOG_ON_RESUME_CALLED, 
+                EventLog.writeEvent(LOG_ON_RESUME_CALLED,
                         r.activity.getComponentName().getClassName());
-                
+
                 r.paused = false;
                 r.stopped = false;
                 if (r.activity.mStartedActivity) {
@@ -2895,7 +2912,7 @@
 
             final int forwardBit = isForward ?
                     WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
-            
+
             // If the window hasn't yet been added to the window manager,
             // and this guy didn't finish itself or start another activity,
             // then go ahead and add the window.
@@ -3014,7 +3031,7 @@
             if (userLeaving) {
                 performUserLeavingActivity(r);
             }
-            
+
             r.activity.mConfigChangeFlags |= configChanges;
             Bundle state = performPauseActivity(token, finished, true);
 
@@ -3191,7 +3208,7 @@
             + " win=" + r.window);
 
         updateVisibility(r, show);
-        
+
         // Tell activity manager we have been stopped.
         try {
             ActivityManagerNative.getDefault().activityStopped(
@@ -3307,7 +3324,7 @@
                 try {
                     r.activity.mCalled = false;
                     mInstrumentation.callActivityOnPause(r.activity);
-                    EventLog.writeEvent(LOG_ON_PAUSE_CALLED, 
+                    EventLog.writeEvent(LOG_ON_PAUSE_CALLED,
                             r.activity.getComponentName().getClassName());
                     if (!r.activity.mCalled) {
                         throw new SuperNotCalledException(
@@ -3364,7 +3381,7 @@
                                 + ": " + e.toString(), e);
                     }
                 }
-                
+
             }
             try {
                 r.activity.mCalled = false;
@@ -3446,7 +3463,7 @@
         unscheduleGcIdler();
 
         Configuration changedConfig = null;
-        
+
         // First: make sure we have the most recent configuration and most
         // recent version of the activity, or skip it if some previous call
         // had taken a more recent version.
@@ -3463,38 +3480,38 @@
                     N--;
                 }
             }
-            
+
             if (tmp == null) {
                 return;
             }
-            
+
             if (mPendingConfiguration != null) {
                 changedConfig = mPendingConfiguration;
                 mPendingConfiguration = null;
             }
         }
-        
+
         // If there was a pending configuration change, execute it first.
         if (changedConfig != null) {
             handleConfigurationChanged(changedConfig);
         }
-        
+
         ActivityRecord r = mActivities.get(tmp.token);
         if (localLOGV) Log.v(TAG, "Handling relaunch of " + r);
         if (r == null) {
             return;
         }
-        
+
         r.activity.mConfigChangeFlags |= configChanges;
         Intent currentIntent = r.activity.mIntent;
-        
+
         Bundle savedState = null;
         if (!r.paused) {
             savedState = performPauseActivity(r.token, false, true);
         }
-        
+
         handleDestroyActivity(r.token, false, configChanges, true);
-        
+
         r.activity = null;
         r.window = null;
         r.hideForNow = false;
@@ -3518,7 +3535,7 @@
         if (savedState != null) {
             r.state = savedState;
         }
-        
+
         handleLaunchActivity(r, currentIntent);
     }
 
@@ -3548,7 +3565,7 @@
             boolean allActivities, Configuration newConfig) {
         ArrayList<ComponentCallbacks> callbacks
                 = new ArrayList<ComponentCallbacks>();
-        
+
         if (mActivities.size() > 0) {
             Iterator<ActivityRecord> it = mActivities.values().iterator();
             while (it.hasNext()) {
@@ -3589,10 +3606,10 @@
         for (int i=0; i<N; i++) {
             callbacks.add(mAllApplications.get(i));
         }
-        
+
         return callbacks;
     }
-    
+
     private final void performConfigurationChanged(
             ComponentCallbacks cb, Configuration config) {
         // Only for Activity objects, check that they actually call up to their
@@ -3602,18 +3619,18 @@
         if (activity != null) {
             activity.mCalled = false;
         }
-        
+
         boolean shouldChangeConfig = false;
         if ((activity == null) || (activity.mCurrentConfig == null)) {
             shouldChangeConfig = true;
         } else {
-            
+
             // If the new config is the same as the config this Activity
             // is already running with then don't bother calling
             // onConfigurationChanged
             int diff = activity.mCurrentConfig.diff(config);
             if (diff != 0) {
-                
+
                 // If this activity doesn't handle any of the config changes
                 // then don't bother calling onConfigurationChanged as we're
                 // going to destroy it.
@@ -3622,10 +3639,10 @@
                 }
             }
         }
-        
+
         if (shouldChangeConfig) {
             cb.onConfigurationChanged(config);
-            
+
             if (activity != null) {
                 if (!activity.mCalled) {
                     throw new SuperNotCalledException(
@@ -3639,17 +3656,17 @@
     }
 
     final void handleConfigurationChanged(Configuration config) {
-        
+
         synchronized (mRelaunchingActivities) {
             if (mPendingConfiguration != null) {
                 config = mPendingConfiguration;
                 mPendingConfiguration = null;
             }
         }
-        
+
         ArrayList<ComponentCallbacks> callbacks
                 = new ArrayList<ComponentCallbacks>();
-        
+
         synchronized(mPackages) {
             if (mConfiguration == null) {
                 mConfiguration = new Configuration();
@@ -3684,10 +3701,10 @@
                     }
                 }
             }
-            
+
             callbacks = collectComponentCallbacksLocked(false, config);
         }
-        
+
         final int N = callbacks.size();
         for (int i=0; i<N; i++) {
             performConfigurationChanged(callbacks.get(i), config);
@@ -3699,7 +3716,7 @@
         if (r == null || r.activity == null) {
             return;
         }
-        
+
         performConfigurationChanged(r.activity, mConfiguration);
     }
 
@@ -3722,7 +3739,7 @@
             Debug.stopMethodTracing();
         }
     }
-    
+
     final void handleLowMemory() {
         ArrayList<ComponentCallbacks> callbacks
                 = new ArrayList<ComponentCallbacks>();
@@ -3730,7 +3747,7 @@
         synchronized(mPackages) {
             callbacks = collectComponentCallbacksLocked(true, null);
         }
-        
+
         final int N = callbacks.size();
         for (int i=0; i<N; i++) {
             callbacks.get(i).onLowMemory();
@@ -3741,7 +3758,7 @@
             int sqliteReleased = SQLiteDatabase.releaseMemory();
             EventLog.writeEvent(SQLITE_MEM_RELEASED_EVENT_LOG_TAG, sqliteReleased);
         }
-        
+
         // Ask graphics to free up as much as possible (font/image caches)
         Canvas.freeCaches();
 
@@ -3788,7 +3805,7 @@
                 == 0) {
             Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEFAULT);
         }
-        
+
         if (data.debugMode != IApplicationThread.DEBUG_OFF) {
             // XXX should have option to change the port.
             Debug.changeDebugPort(8100);
@@ -4213,6 +4230,8 @@
     }
 
     public static final void main(String[] args) {
+        SamplingProfilerIntegration.start();
+
         Process.setArgV0("<pre-initialized>");
 
         Looper.prepareMainLooper();
@@ -4228,8 +4247,11 @@
 
         thread.detach();
         String name;
-        if (thread.mInitialApplication != null) name = thread.mInitialApplication.getPackageName();
-        else name = "<unknown>";
+        if (thread.mInitialApplication != null) {
+            name = thread.mInitialApplication.getPackageName();
+        } else {
+            name = "<unknown>";
+        }
         Log.i(TAG, "Main thread of " + name + " is now exiting");
     }
 }