Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2007 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 package com.example.android.apis.app;
     18 
     19 import android.app.Activity;
     20 import android.app.Notification;
     21 import android.app.NotificationManager;
     22 import android.app.PendingIntent;
     23 import android.app.Service;
     24 import android.content.ComponentName;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.ServiceConnection;
     28 import android.os.Bundle;
     29 import android.os.RemoteException;
     30 import android.os.Handler;
     31 import android.os.IBinder;
     32 import android.os.Message;
     33 import android.os.Process;
     34 import android.os.RemoteCallbackList;
     35 import android.util.Log;
     36 import android.view.View;
     37 import android.view.View.OnClickListener;
     38 import android.widget.Button;
     39 import android.widget.TextView;
     40 import android.widget.Toast;
     41 
     42 // Need the following import to get access to the app resources, since this
     43 // class is in a sub-package.
     44 import com.example.android.apis.R;
     45 
     46 /**
     47  * This is an example of implementing an application service that runs in a
     48  * different process than the application.  Because it can be in another
     49  * process, we must use IPC to interact with it.  The
     50  * {@link Controller} and {@link Binding} classes
     51  * show how to interact with the service.
     52  *
     53  * <p>Note that most applications <strong>do not</strong> need to deal with
     54  * the complexity shown here.  If your application simply has a service
     55  * running in its own process, the {@link LocalService} sample shows a much
     56  * simpler way to interact with it.
     57  */
     58 public class RemoteService extends Service {
     59     /**
     60      * This is a list of callbacks that have been registered with the
     61      * service.  Note that this is package scoped (instead of private) so
     62      * that it can be accessed more efficiently from inner classes.
     63      */
     64     final RemoteCallbackList<IRemoteServiceCallback> mCallbacks
     65             = new RemoteCallbackList<IRemoteServiceCallback>();
     66 
     67     int mValue = 0;
     68     NotificationManager mNM;
     69 
     70     @Override
     71     public void onCreate() {
     72         mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
     73 
     74         // Display a notification about us starting.
     75         showNotification();
     76 
     77         // While this service is running, it will continually increment a
     78         // number.  Send the first message that is used to perform the
     79         // increment.
     80         mHandler.sendEmptyMessage(REPORT_MSG);
     81     }
     82 
     83     @Override
     84     public int onStartCommand(Intent intent, int flags, int startId) {
     85         Log.i("RemoteService", "Received start id " + startId + ": " + intent);
     86         return START_NOT_STICKY;
     87     }
     88 
     89     @Override
     90     public void onDestroy() {
     91         // Cancel the persistent notification.
     92         mNM.cancel(R.string.remote_service_started);
     93 
     94         // Tell the user we stopped.
     95         Toast.makeText(this, R.string.remote_service_stopped, Toast.LENGTH_SHORT).show();
     96 
     97         // Unregister all callbacks.
     98         mCallbacks.kill();
     99 
    100         // Remove the next pending message to increment the counter, stopping
    101         // the increment loop.
    102         mHandler.removeMessages(REPORT_MSG);
    103     }
    104 
    105 // BEGIN_INCLUDE(exposing_a_service)
    106     @Override
    107     public IBinder onBind(Intent intent) {
    108         // Select the interface to return.  If your service only implements
    109         // a single interface, you can just return it here without checking
    110         // the Intent.
    111         if (IRemoteService.class.getName().equals(intent.getAction())) {
    112             return mBinder;
    113         }
    114         if (ISecondary.class.getName().equals(intent.getAction())) {
    115             return mSecondaryBinder;
    116         }
    117         return null;
    118     }
    119 
    120     /**
    121      * The IRemoteInterface is defined through IDL
    122      */
    123     private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
    124         public void registerCallback(IRemoteServiceCallback cb) {
    125             if (cb != null) mCallbacks.register(cb);
    126         }
    127         public void unregisterCallback(IRemoteServiceCallback cb) {
    128             if (cb != null) mCallbacks.unregister(cb);
    129         }
    130     };
    131 
    132     /**
    133      * A secondary interface to the service.
    134      */
    135     private final ISecondary.Stub mSecondaryBinder = new ISecondary.Stub() {
    136         public int getPid() {
    137             return Process.myPid();
    138         }
    139         public void basicTypes(int anInt, long aLong, boolean aBoolean,
    140                 float aFloat, double aDouble, String aString) {
    141         }
    142     };
    143 // END_INCLUDE(exposing_a_service)
    144 
    145     @Override
    146     public void onTaskRemoved(Intent rootIntent) {
    147         Toast.makeText(this, "Task removed: " + rootIntent, Toast.LENGTH_LONG).show();
    148     }
    149 
    150     private static final int REPORT_MSG = 1;
    151 
    152     /**
    153      * Our Handler used to execute operations on the main thread.  This is used
    154      * to schedule increments of our value.
    155      */
    156     private final Handler mHandler = new Handler() {
    157         @Override public void handleMessage(Message msg) {
    158             switch (msg.what) {
    159 
    160                 // It is time to bump the value!
    161                 case REPORT_MSG: {
    162                     // Up it goes.
    163                     int value = ++mValue;
    164 
    165                     // Broadcast to all clients the new value.
    166                     final int N = mCallbacks.beginBroadcast();
    167                     for (int i=0; i<N; i++) {
    168                         try {
    169                             mCallbacks.getBroadcastItem(i).valueChanged(value);
    170                         } catch (RemoteException e) {
    171                             // The RemoteCallbackList will take care of removing
    172                             // the dead object for us.
    173                         }
    174                     }
    175                     mCallbacks.finishBroadcast();
    176 
    177                     // Repeat every 1 second.
    178                     sendMessageDelayed(obtainMessage(REPORT_MSG), 1*1000);
    179                 } break;
    180                 default:
    181                     super.handleMessage(msg);
    182             }
    183         }
    184     };
    185 
    186     /**
    187      * Show a notification while this service is running.
    188      */
    189     private void showNotification() {
    190         // In this sample, we'll use the same text for the ticker and the expanded notification
    191         CharSequence text = getText(R.string.remote_service_started);
    192 
    193         // The PendingIntent to launch our activity if the user selects this notification
    194         PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
    195                 new Intent(this, Controller.class), 0);
    196 
    197         // Set the info for the views that show in the notification panel.
    198         Notification notification = new Notification.Builder(this)
    199                 .setSmallIcon(R.drawable.stat_sample)  // the status icon
    200                 .setTicker(text)  // the status text
    201                 .setWhen(System.currentTimeMillis())  // the time stamp
    202                 .setContentTitle(getText(R.string.remote_service_label))  // the label of the entry
    203                 .setContentText(text)  // the contents of the entry
    204                 .setContentIntent(contentIntent)  // The intent to send when the entry is clicked
    205                 .build();
    206 
    207         // Send the notification.
    208         // We use a string id because it is a unique number.  We use it later to cancel.
    209         mNM.notify(R.string.remote_service_started, notification);
    210     }
    211 
    212     // ----------------------------------------------------------------------
    213 
    214     /**
    215      * <p>Example of explicitly starting and stopping the remove service.
    216      * This demonstrates the implementation of a service that runs in a different
    217      * process than the rest of the application, which is explicitly started and stopped
    218      * as desired.</p>
    219      *
    220      * <p>Note that this is implemented as an inner class only keep the sample
    221      * all together; typically this code would appear in some separate class.
    222      */
    223     public static class Controller extends Activity {
    224         @Override
    225         protected void onCreate(Bundle savedInstanceState) {
    226             super.onCreate(savedInstanceState);
    227 
    228             setContentView(R.layout.remote_service_controller);
    229 
    230             // Watch for button clicks.
    231             Button button = (Button)findViewById(R.id.start);
    232             button.setOnClickListener(mStartListener);
    233             button = (Button)findViewById(R.id.stop);
    234             button.setOnClickListener(mStopListener);
    235         }
    236 
    237         private OnClickListener mStartListener = new OnClickListener() {
    238             public void onClick(View v) {
    239                 // Make sure the service is started.  It will continue running
    240                 // until someone calls stopService().
    241                 // We use an action code here, instead of explictly supplying
    242                 // the component name, so that other packages can replace
    243                 // the service.
    244                 startService(new Intent(Controller.this, RemoteService.class));
    245             }
    246         };
    247 
    248         private OnClickListener mStopListener = new OnClickListener() {
    249             public void onClick(View v) {
    250                 // Cancel a previous call to startService().  Note that the
    251                 // service will not actually stop at this point if there are
    252                 // still bound clients.
    253                 stopService(new Intent(Controller.this, RemoteService.class));
    254             }
    255         };
    256     }
    257 
    258     // ----------------------------------------------------------------------
    259 
    260     /**
    261      * Example of binding and unbinding to the remote service.
    262      * This demonstrates the implementation of a service which the client will
    263      * bind to, interacting with it through an aidl interface.</p>
    264      *
    265      * <p>Note that this is implemented as an inner class only keep the sample
    266      * all together; typically this code would appear in some separate class.
    267      */
    268  // BEGIN_INCLUDE(calling_a_service)
    269     public static class Binding extends Activity {
    270         /** The primary interface we will be calling on the service. */
    271         IRemoteService mService = null;
    272         /** Another interface we use on the service. */
    273         ISecondary mSecondaryService = null;
    274 
    275         Button mKillButton;
    276         TextView mCallbackText;
    277 
    278         private boolean mIsBound;
    279 
    280         /**
    281          * Standard initialization of this activity.  Set up the UI, then wait
    282          * for the user to poke it before doing anything.
    283          */
    284         @Override
    285         protected void onCreate(Bundle savedInstanceState) {
    286             super.onCreate(savedInstanceState);
    287 
    288             setContentView(R.layout.remote_service_binding);
    289 
    290             // Watch for button clicks.
    291             Button button = (Button)findViewById(R.id.bind);
    292             button.setOnClickListener(mBindListener);
    293             button = (Button)findViewById(R.id.unbind);
    294             button.setOnClickListener(mUnbindListener);
    295             mKillButton = (Button)findViewById(R.id.kill);
    296             mKillButton.setOnClickListener(mKillListener);
    297             mKillButton.setEnabled(false);
    298 
    299             mCallbackText = (TextView)findViewById(R.id.callback);
    300             mCallbackText.setText("Not attached.");
    301         }
    302 
    303         /**
    304          * Class for interacting with the main interface of the service.
    305          */
    306         private ServiceConnection mConnection = new ServiceConnection() {
    307             public void onServiceConnected(ComponentName className,
    308                     IBinder service) {
    309                 // This is called when the connection with the service has been
    310                 // established, giving us the service object we can use to
    311                 // interact with the service.  We are communicating with our
    312                 // service through an IDL interface, so get a client-side
    313                 // representation of that from the raw service object.
    314                 mService = IRemoteService.Stub.asInterface(service);
    315                 mKillButton.setEnabled(true);
    316                 mCallbackText.setText("Attached.");
    317 
    318                 // We want to monitor the service for as long as we are
    319                 // connected to it.
    320                 try {
    321                     mService.registerCallback(mCallback);
    322                 } catch (RemoteException e) {
    323                     // In this case the service has crashed before we could even
    324                     // do anything with it; we can count on soon being
    325                     // disconnected (and then reconnected if it can be restarted)
    326                     // so there is no need to do anything here.
    327                 }
    328 
    329                 // As part of the sample, tell the user what happened.
    330                 Toast.makeText(Binding.this, R.string.remote_service_connected,
    331                         Toast.LENGTH_SHORT).show();
    332             }
    333 
    334             public void onServiceDisconnected(ComponentName className) {
    335                 // This is called when the connection with the service has been
    336                 // unexpectedly disconnected -- that is, its process crashed.
    337                 mService = null;
    338                 mKillButton.setEnabled(false);
    339                 mCallbackText.setText("Disconnected.");
    340 
    341                 // As part of the sample, tell the user what happened.
    342                 Toast.makeText(Binding.this, R.string.remote_service_disconnected,
    343                         Toast.LENGTH_SHORT).show();
    344             }
    345         };
    346 
    347         /**
    348          * Class for interacting with the secondary interface of the service.
    349          */
    350         private ServiceConnection mSecondaryConnection = new ServiceConnection() {
    351             public void onServiceConnected(ComponentName className,
    352                     IBinder service) {
    353                 // Connecting to a secondary interface is the same as any
    354                 // other interface.
    355                 mSecondaryService = ISecondary.Stub.asInterface(service);
    356                 mKillButton.setEnabled(true);
    357             }
    358 
    359             public void onServiceDisconnected(ComponentName className) {
    360                 mSecondaryService = null;
    361                 mKillButton.setEnabled(false);
    362             }
    363         };
    364 
    365         private OnClickListener mBindListener = new OnClickListener() {
    366             public void onClick(View v) {
    367                 // Establish a couple connections with the service, binding
    368                 // by interface names.  This allows other applications to be
    369                 // installed that replace the remote service by implementing
    370                 // the same interface.
    371                 Intent intent = new Intent(Binding.this, RemoteService.class);
    372                 intent.setAction(IRemoteService.class.getName());
    373                 bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    374                 intent.setAction(ISecondary.class.getName());
    375                 bindService(intent, mSecondaryConnection, Context.BIND_AUTO_CREATE);
    376                 mIsBound = true;
    377                 mCallbackText.setText("Binding.");
    378             }
    379         };
    380 
    381         private OnClickListener mUnbindListener = new OnClickListener() {
    382             public void onClick(View v) {
    383                 if (mIsBound) {
    384                     // If we have received the service, and hence registered with
    385                     // it, then now is the time to unregister.
    386                     if (mService != null) {
    387                         try {
    388                             mService.unregisterCallback(mCallback);
    389                         } catch (RemoteException e) {
    390                             // There is nothing special we need to do if the service
    391                             // has crashed.
    392                         }
    393                     }
    394 
    395                     // Detach our existing connection.
    396                     unbindService(mConnection);
    397                     unbindService(mSecondaryConnection);
    398                     mKillButton.setEnabled(false);
    399                     mIsBound = false;
    400                     mCallbackText.setText("Unbinding.");
    401                 }
    402             }
    403         };
    404 
    405         private OnClickListener mKillListener = new OnClickListener() {
    406             public void onClick(View v) {
    407                 // To kill the process hosting our service, we need to know its
    408                 // PID.  Conveniently our service has a call that will return
    409                 // to us that information.
    410                 if (mSecondaryService != null) {
    411                     try {
    412                         int pid = mSecondaryService.getPid();
    413                         // Note that, though this API allows us to request to
    414                         // kill any process based on its PID, the kernel will
    415                         // still impose standard restrictions on which PIDs you
    416                         // are actually able to kill.  Typically this means only
    417                         // the process running your application and any additional
    418                         // processes created by that app as shown here; packages
    419                         // sharing a common UID will also be able to kill each
    420                         // other's processes.
    421                         Process.killProcess(pid);
    422                         mCallbackText.setText("Killed service process.");
    423                     } catch (RemoteException ex) {
    424                         // Recover gracefully from the process hosting the
    425                         // server dying.
    426                         // Just for purposes of the sample, put up a notification.
    427                         Toast.makeText(Binding.this,
    428                                 R.string.remote_call_failed,
    429                                 Toast.LENGTH_SHORT).show();
    430                     }
    431                 }
    432             }
    433         };
    434 
    435         // ----------------------------------------------------------------------
    436         // Code showing how to deal with callbacks.
    437         // ----------------------------------------------------------------------
    438 
    439         /**
    440          * This implementation is used to receive callbacks from the remote
    441          * service.
    442          */
    443         private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
    444             /**
    445              * This is called by the remote service regularly to tell us about
    446              * new values.  Note that IPC calls are dispatched through a thread
    447              * pool running in each process, so the code executing here will
    448              * NOT be running in our main thread like most other things -- so,
    449              * to update the UI, we need to use a Handler to hop over there.
    450              */
    451             public void valueChanged(int value) {
    452                 mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0));
    453             }
    454         };
    455 
    456         private static final int BUMP_MSG = 1;
    457 
    458         private Handler mHandler = new Handler() {
    459             @Override public void handleMessage(Message msg) {
    460                 switch (msg.what) {
    461                     case BUMP_MSG:
    462                         mCallbackText.setText("Received from service: " + msg.arg1);
    463                         break;
    464                     default:
    465                         super.handleMessage(msg);
    466                 }
    467             }
    468 
    469         };
    470     }
    471 // END_INCLUDE(calling_a_service)
    472 
    473     // ----------------------------------------------------------------------
    474 
    475     /**
    476      * Examples of behavior of different bind flags.</p>
    477      */
    478  // BEGIN_INCLUDE(calling_a_service)
    479     public static class BindingOptions extends Activity {
    480         ServiceConnection mCurConnection;
    481         TextView mCallbackText;
    482         Intent mBindIntent;
    483 
    484         class MyServiceConnection implements ServiceConnection {
    485             final boolean mUnbindOnDisconnect;
    486 
    487             public MyServiceConnection() {
    488                 mUnbindOnDisconnect = false;
    489             }
    490 
    491             public MyServiceConnection(boolean unbindOnDisconnect) {
    492                 mUnbindOnDisconnect = unbindOnDisconnect;
    493             }
    494 
    495             public void onServiceConnected(ComponentName className,
    496                     IBinder service) {
    497                 if (mCurConnection != this) {
    498                     return;
    499                 }
    500                 mCallbackText.setText("Attached.");
    501                 Toast.makeText(BindingOptions.this, R.string.remote_service_connected,
    502                         Toast.LENGTH_SHORT).show();
    503             }
    504 
    505             public void onServiceDisconnected(ComponentName className) {
    506                 if (mCurConnection != this) {
    507                     return;
    508                 }
    509                 mCallbackText.setText("Disconnected.");
    510                 Toast.makeText(BindingOptions.this, R.string.remote_service_disconnected,
    511                         Toast.LENGTH_SHORT).show();
    512                 if (mUnbindOnDisconnect) {
    513                     unbindService(this);
    514                     mCurConnection = null;
    515                     Toast.makeText(BindingOptions.this, R.string.remote_service_unbind_disconn,
    516                             Toast.LENGTH_SHORT).show();
    517                 }
    518             }
    519         }
    520 
    521         /**
    522          * Standard initialization of this activity.  Set up the UI, then wait
    523          * for the user to poke it before doing anything.
    524          */
    525         @Override
    526         protected void onCreate(Bundle savedInstanceState) {
    527             super.onCreate(savedInstanceState);
    528 
    529             setContentView(R.layout.remote_binding_options);
    530 
    531             // Watch for button clicks.
    532             Button button = (Button)findViewById(R.id.bind_normal);
    533             button.setOnClickListener(mBindNormalListener);
    534             button = (Button)findViewById(R.id.bind_not_foreground);
    535             button.setOnClickListener(mBindNotForegroundListener);
    536             button = (Button)findViewById(R.id.bind_above_client);
    537             button.setOnClickListener(mBindAboveClientListener);
    538             button = (Button)findViewById(R.id.bind_allow_oom);
    539             button.setOnClickListener(mBindAllowOomListener);
    540             button = (Button)findViewById(R.id.bind_waive_priority);
    541             button.setOnClickListener(mBindWaivePriorityListener);
    542             button = (Button)findViewById(R.id.bind_important);
    543             button.setOnClickListener(mBindImportantListener);
    544             button = (Button)findViewById(R.id.bind_with_activity);
    545             button.setOnClickListener(mBindWithActivityListener);
    546             button = (Button)findViewById(R.id.unbind);
    547             button.setOnClickListener(mUnbindListener);
    548 
    549             mCallbackText = (TextView)findViewById(R.id.callback);
    550             mCallbackText.setText("Not attached.");
    551 
    552             mBindIntent = new Intent(this, RemoteService.class);
    553             mBindIntent.setAction(IRemoteService.class.getName());
    554         }
    555 
    556         private OnClickListener mBindNormalListener = new OnClickListener() {
    557             public void onClick(View v) {
    558                 if (mCurConnection != null) {
    559                     unbindService(mCurConnection);
    560                     mCurConnection = null;
    561                 }
    562                 ServiceConnection conn = new MyServiceConnection();
    563                 if (bindService(mBindIntent, conn, Context.BIND_AUTO_CREATE)) {
    564                     mCurConnection = conn;
    565                 }
    566             }
    567         };
    568 
    569         private OnClickListener mBindNotForegroundListener = new OnClickListener() {
    570             public void onClick(View v) {
    571                 if (mCurConnection != null) {
    572                     unbindService(mCurConnection);
    573                     mCurConnection = null;
    574                 }
    575                 ServiceConnection conn = new MyServiceConnection();
    576                 if (bindService(mBindIntent, conn,
    577                         Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND)) {
    578                     mCurConnection = conn;
    579                 }
    580             }
    581         };
    582 
    583         private OnClickListener mBindAboveClientListener = new OnClickListener() {
    584             public void onClick(View v) {
    585                 if (mCurConnection != null) {
    586                     unbindService(mCurConnection);
    587                     mCurConnection = null;
    588                 }
    589                 ServiceConnection conn = new MyServiceConnection();
    590                 if (bindService(mBindIntent,
    591                         conn, Context.BIND_AUTO_CREATE | Context.BIND_ABOVE_CLIENT)) {
    592                     mCurConnection = conn;
    593                 }
    594             }
    595         };
    596 
    597         private OnClickListener mBindAllowOomListener = new OnClickListener() {
    598             public void onClick(View v) {
    599                 if (mCurConnection != null) {
    600                     unbindService(mCurConnection);
    601                     mCurConnection = null;
    602                 }
    603                 ServiceConnection conn = new MyServiceConnection();
    604                 if (bindService(mBindIntent, conn,
    605                         Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_OOM_MANAGEMENT)) {
    606                     mCurConnection = conn;
    607                 }
    608             }
    609         };
    610 
    611         private OnClickListener mBindWaivePriorityListener = new OnClickListener() {
    612             public void onClick(View v) {
    613                 if (mCurConnection != null) {
    614                     unbindService(mCurConnection);
    615                     mCurConnection = null;
    616                 }
    617                 ServiceConnection conn = new MyServiceConnection(true);
    618                 if (bindService(mBindIntent, conn,
    619                         Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY)) {
    620                     mCurConnection = conn;
    621                 }
    622             }
    623         };
    624 
    625         private OnClickListener mBindImportantListener = new OnClickListener() {
    626             public void onClick(View v) {
    627                 if (mCurConnection != null) {
    628                     unbindService(mCurConnection);
    629                     mCurConnection = null;
    630                 }
    631                 ServiceConnection conn = new MyServiceConnection();
    632                 if (bindService(mBindIntent, conn,
    633                         Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT)) {
    634                     mCurConnection = conn;
    635                 }
    636             }
    637         };
    638 
    639         private OnClickListener mBindWithActivityListener = new OnClickListener() {
    640             public void onClick(View v) {
    641                 if (mCurConnection != null) {
    642                     unbindService(mCurConnection);
    643                     mCurConnection = null;
    644                 }
    645                 ServiceConnection conn = new MyServiceConnection();
    646                 if (bindService(mBindIntent, conn,
    647                         Context.BIND_AUTO_CREATE | Context.BIND_ADJUST_WITH_ACTIVITY
    648                         | Context.BIND_WAIVE_PRIORITY)) {
    649                     mCurConnection = conn;
    650                 }
    651             }
    652         };
    653 
    654         private OnClickListener mUnbindListener = new OnClickListener() {
    655             public void onClick(View v) {
    656                 if (mCurConnection != null) {
    657                     unbindService(mCurConnection);
    658                     mCurConnection = null;
    659                 }
    660             }
    661         };
    662     }
    663 }
    664