OSDN Git Service

c1e75c0b966669879bfb446477b97c2414c4456b
[android-x86/packages-apps-Settings.git] / src / com / android / settings / bluetooth / CachedBluetoothDevice.java
1 /*
2  * Copyright (C) 2008 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.android.settings.bluetooth;
18
19 import android.bluetooth.BluetoothClass;
20 import android.bluetooth.BluetoothDevice;
21 import android.bluetooth.BluetoothProfile;
22 import android.content.Context;
23 import android.content.SharedPreferences;
24 import android.os.ParcelUuid;
25 import android.os.SystemClock;
26 import android.text.TextUtils;
27 import android.util.Log;
28 import android.bluetooth.BluetoothAdapter;
29
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.List;
35
36 /**
37  * CachedBluetoothDevice represents a remote Bluetooth device. It contains
38  * attributes of the device (such as the address, name, RSSI, etc.) and
39  * functionality that can be performed on the device (connect, pair, disconnect,
40  * etc.).
41  */
42 final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
43     private static final String TAG = "CachedBluetoothDevice";
44     private static final boolean DEBUG = Utils.V;
45
46     private final Context mContext;
47     private final LocalBluetoothAdapter mLocalAdapter;
48     private final LocalBluetoothProfileManager mProfileManager;
49     private final BluetoothDevice mDevice;
50     private String mName;
51     private short mRssi;
52     private BluetoothClass mBtClass;
53     private HashMap<LocalBluetoothProfile, Integer> mProfileConnectionState;
54
55     private final List<LocalBluetoothProfile> mProfiles =
56             new ArrayList<LocalBluetoothProfile>();
57
58     // List of profiles that were previously in mProfiles, but have been removed
59     private final List<LocalBluetoothProfile> mRemovedProfiles =
60             new ArrayList<LocalBluetoothProfile>();
61
62     // Device supports PANU but not NAP: remove PanProfile after device disconnects from NAP
63     private boolean mLocalNapRoleConnected;
64
65     private boolean mVisible;
66
67     private int mPhonebookPermissionChoice;
68
69     private int mMessagePermissionChoice;
70
71     private int mMessageRejectedTimes;
72
73     private final Collection<Callback> mCallbacks = new ArrayList<Callback>();
74
75     // Following constants indicate the user's choices of Phone book/message access settings
76     // User hasn't made any choice or settings app has wiped out the memory
77     public final static int ACCESS_UNKNOWN = 0;
78     // User has accepted the connection and let Settings app remember the decision
79     public final static int ACCESS_ALLOWED = 1;
80     // User has rejected the connection and let Settings app remember the decision
81     public final static int ACCESS_REJECTED = 2;
82
83     // how many times did User reject the connection to make the rejected persist.
84     final static int PERSIST_REJECTED_TIMES_LIMIT = 2;
85
86     private final static String PHONEBOOK_PREFS_NAME = "bluetooth_phonebook_permission";
87     private final static String MESSAGE_PREFS_NAME = "bluetooth_message_permission";
88     private final static String MESSAGE_REJECT_TIMES = "bluetooth_message_reject";
89
90     /**
91      * When we connect to multiple profiles, we only want to display a single
92      * error even if they all fail. This tracks that state.
93      */
94     private boolean mIsConnectingErrorPossible;
95
96     /**
97      * Last time a bt profile auto-connect was attempted.
98      * If an ACTION_UUID intent comes in within
99      * MAX_UUID_DELAY_FOR_AUTO_CONNECT milliseconds, we will try auto-connect
100      * again with the new UUIDs
101      */
102     private long mConnectAttempted;
103
104     // See mConnectAttempted
105     private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000;
106
107     /** Auto-connect after pairing only if locally initiated. */
108     private boolean mConnectAfterPairing;
109
110     /**
111      * Describes the current device and profile for logging.
112      *
113      * @param profile Profile to describe
114      * @return Description of the device and profile
115      */
116     private String describe(LocalBluetoothProfile profile) {
117         StringBuilder sb = new StringBuilder();
118         sb.append("Address:").append(mDevice);
119         if (profile != null) {
120             sb.append(" Profile:").append(profile);
121         }
122
123         return sb.toString();
124     }
125
126     void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) {
127         if (Utils.D) {
128             Log.d(TAG, "onProfileStateChanged: profile " + profile +
129                     " newProfileState " + newProfileState);
130         }
131         if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF)
132         {
133             if (Utils.D) Log.d(TAG, " BT Turninig Off...Profile conn state change ignored...");
134             return;
135         }
136         mProfileConnectionState.put(profile, newProfileState);
137         if (newProfileState == BluetoothProfile.STATE_CONNECTED) {
138             if (!mProfiles.contains(profile)) {
139                 mRemovedProfiles.remove(profile);
140                 mProfiles.add(profile);
141                 if (profile instanceof PanProfile &&
142                         ((PanProfile) profile).isLocalRoleNap(mDevice)) {
143                     // Device doesn't support NAP, so remove PanProfile on disconnect
144                     mLocalNapRoleConnected = true;
145                 }
146             }
147             if (profile instanceof MapProfile) {
148                 profile.setPreferred(mDevice, true);
149             }
150         } else if (profile instanceof MapProfile &&
151                 newProfileState == BluetoothProfile.STATE_DISCONNECTED) {
152             if (mProfiles.contains(profile)) {
153                 mRemovedProfiles.add(profile);
154                 mProfiles.remove(profile);
155             }
156             profile.setPreferred(mDevice, false);
157         } else if (mLocalNapRoleConnected && profile instanceof PanProfile &&
158                 ((PanProfile) profile).isLocalRoleNap(mDevice) &&
159                 newProfileState == BluetoothProfile.STATE_DISCONNECTED) {
160             Log.d(TAG, "Removing PanProfile from device after NAP disconnect");
161             mProfiles.remove(profile);
162             mRemovedProfiles.add(profile);
163             mLocalNapRoleConnected = false;
164         }
165     }
166
167     CachedBluetoothDevice(Context context,
168                           LocalBluetoothAdapter adapter,
169                           LocalBluetoothProfileManager profileManager,
170                           BluetoothDevice device) {
171         mContext = context;
172         mLocalAdapter = adapter;
173         mProfileManager = profileManager;
174         mDevice = device;
175         mProfileConnectionState = new HashMap<LocalBluetoothProfile, Integer>();
176         fillData();
177     }
178
179     void disconnect() {
180         for (LocalBluetoothProfile profile : mProfiles) {
181             disconnect(profile);
182         }
183         // Disconnect  PBAP server in case its connected
184         // This is to ensure all the profiles are disconnected as some CK/Hs do not
185         // disconnect  PBAP connection when HF connection is brought down
186         PbapServerProfile PbapProfile = mProfileManager.getPbapProfile();
187         if (PbapProfile.getConnectionStatus(mDevice) == BluetoothProfile.STATE_CONNECTED)
188         {
189             PbapProfile.disconnect(mDevice);
190         }
191     }
192
193     void disconnect(LocalBluetoothProfile profile) {
194         if (profile.disconnect(mDevice)) {
195             if (Utils.D) {
196                 Log.d(TAG, "Command sent successfully:DISCONNECT " + describe(profile));
197             }
198         }
199     }
200
201     void connect(boolean connectAllProfiles) {
202         if (!ensurePaired()) {
203             return;
204         }
205
206         mConnectAttempted = SystemClock.elapsedRealtime();
207         connectWithoutResettingTimer(connectAllProfiles);
208     }
209
210     void onBondingDockConnect() {
211         // Attempt to connect if UUIDs are available. Otherwise,
212         // we will connect when the ACTION_UUID intent arrives.
213         connect(false);
214     }
215
216     private void connectWithoutResettingTimer(boolean connectAllProfiles) {
217         // Try to initialize the profiles if they were not.
218         if (mProfiles.isEmpty()) {
219             // if mProfiles is empty, then do not invoke updateProfiles. This causes a race
220             // condition with carkits during pairing, wherein RemoteDevice.UUIDs have been updated
221             // from bluetooth stack but ACTION.uuid is not sent yet.
222             // Eventually ACTION.uuid will be received which shall trigger the connection of the
223             // various profiles
224             // If UUIDs are not available yet, connect will be happen
225             // upon arrival of the ACTION_UUID intent.
226             Log.d(TAG, "No profiles. Maybe we will connect later");
227             return;
228         }
229
230         // Reset the only-show-one-error-dialog tracking variable
231         mIsConnectingErrorPossible = true;
232
233         int preferredProfiles = 0;
234         for (LocalBluetoothProfile profile : mProfiles) {
235             if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) {
236                 if (profile.isPreferred(mDevice)) {
237                     ++preferredProfiles;
238                     connectInt(profile);
239                 }
240             }
241         }
242         if (DEBUG) Log.d(TAG, "Preferred profiles = " + preferredProfiles);
243
244         if (preferredProfiles == 0) {
245             connectAutoConnectableProfiles();
246         }
247     }
248
249     private void connectAutoConnectableProfiles() {
250         if (!ensurePaired()) {
251             return;
252         }
253         // Reset the only-show-one-error-dialog tracking variable
254         mIsConnectingErrorPossible = true;
255
256         for (LocalBluetoothProfile profile : mProfiles) {
257             if (profile.isAutoConnectable()) {
258                 profile.setPreferred(mDevice, true);
259                 connectInt(profile);
260             }
261         }
262     }
263
264     /**
265      * Connect this device to the specified profile.
266      *
267      * @param profile the profile to use with the remote device
268      */
269     void connectProfile(LocalBluetoothProfile profile) {
270         mConnectAttempted = SystemClock.elapsedRealtime();
271         // Reset the only-show-one-error-dialog tracking variable
272         mIsConnectingErrorPossible = true;
273         connectInt(profile);
274         // Refresh the UI based on profile.connect() call
275         refresh();
276     }
277
278     synchronized void connectInt(LocalBluetoothProfile profile) {
279         if (!ensurePaired()) {
280             return;
281         }
282         if (profile.connect(mDevice)) {
283             if (Utils.D) {
284                 Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile));
285             }
286             return;
287         }
288         Log.i(TAG, "Failed to connect " + profile.toString() + " to " + mName);
289     }
290
291     private boolean ensurePaired() {
292         if (getBondState() == BluetoothDevice.BOND_NONE) {
293             startPairing();
294             return false;
295         } else {
296             return true;
297         }
298     }
299
300     boolean startPairing() {
301         // Pairing is unreliable while scanning, so cancel discovery
302         if (mLocalAdapter.isDiscovering()) {
303             mLocalAdapter.cancelDiscovery();
304         }
305
306         if (!mDevice.createBond()) {
307             return false;
308         }
309
310         mConnectAfterPairing = true;  // auto-connect after pairing
311         return true;
312     }
313
314     /**
315      * Return true if user initiated pairing on this device. The message text is
316      * slightly different for local vs. remote initiated pairing dialogs.
317      */
318     boolean isUserInitiatedPairing() {
319         return mConnectAfterPairing;
320     }
321
322     void unpair() {
323         int state = getBondState();
324
325         if (state == BluetoothDevice.BOND_BONDING) {
326             mDevice.cancelBondProcess();
327         }
328
329         if (state != BluetoothDevice.BOND_NONE) {
330             final BluetoothDevice dev = mDevice;
331             if (dev != null) {
332                 final boolean successful = dev.removeBond();
333                 if (successful) {
334                     if (Utils.D) {
335                         Log.d(TAG, "Command sent successfully:REMOVE_BOND " + describe(null));
336                     }
337                 } else if (Utils.V) {
338                     Log.v(TAG, "Framework rejected command immediately:REMOVE_BOND " +
339                             describe(null));
340                 }
341             }
342         }
343     }
344
345     int getProfileConnectionState(LocalBluetoothProfile profile) {
346         if (mProfileConnectionState == null ||
347                 mProfileConnectionState.get(profile) == null) {
348             // If cache is empty make the binder call to get the state
349             int state = profile.getConnectionStatus(mDevice);
350             mProfileConnectionState.put(profile, state);
351         }
352         return mProfileConnectionState.get(profile);
353     }
354
355     public void clearProfileConnectionState ()
356     {
357         if (Utils.D) {
358             Log.d(TAG," Clearing all connection state for dev:" + mDevice.getName());
359         }
360         for (LocalBluetoothProfile profile :getProfiles()) {
361             mProfileConnectionState.put(profile, BluetoothProfile.STATE_DISCONNECTED);
362         }
363     }
364
365     // TODO: do any of these need to run async on a background thread?
366     private void fillData() {
367         fetchName();
368         fetchBtClass();
369         updateProfiles();
370         fetchPhonebookPermissionChoice();
371         fetchMessagePermissionChoice();
372         fetchMessageRejectTimes();
373
374         mVisible = false;
375         dispatchAttributesChanged();
376     }
377
378     BluetoothDevice getDevice() {
379         return mDevice;
380     }
381
382     String getName() {
383         return mName;
384     }
385
386     void setName(String name) {
387         if (!mName.equals(name)) {
388             if (TextUtils.isEmpty(name)) {
389                 // TODO: use friendly name for unknown device (bug 1181856)
390                 mName = mDevice.getAddress();
391             } else {
392                 mName = name;
393                 mDevice.setAlias(name);
394             }
395             dispatchAttributesChanged();
396         }
397     }
398
399     void refreshName() {
400         fetchName();
401         dispatchAttributesChanged();
402     }
403
404     private void fetchName() {
405         mName = mDevice.getAliasName();
406
407         if (TextUtils.isEmpty(mName)) {
408             mName = mDevice.getAddress();
409             if (DEBUG) Log.d(TAG, "Device has no name (yet), use address: " + mName);
410         }
411     }
412
413     void refresh() {
414         dispatchAttributesChanged();
415     }
416
417     boolean isVisible() {
418         return mVisible;
419     }
420
421     void setVisible(boolean visible) {
422         if (mVisible != visible) {
423             mVisible = visible;
424             dispatchAttributesChanged();
425         }
426     }
427
428     int getBondState() {
429         return mDevice.getBondState();
430     }
431
432     void setRssi(short rssi) {
433         if (mRssi != rssi) {
434             mRssi = rssi;
435             dispatchAttributesChanged();
436         }
437     }
438
439     /**
440      * Checks whether we are connected to this device (any profile counts).
441      *
442      * @return Whether it is connected.
443      */
444     boolean isConnected() {
445         for (LocalBluetoothProfile profile : mProfiles) {
446             int status = getProfileConnectionState(profile);
447             if (status == BluetoothProfile.STATE_CONNECTED) {
448                 return true;
449             }
450         }
451
452         return false;
453     }
454
455     boolean isConnectedProfile(LocalBluetoothProfile profile) {
456         int status = getProfileConnectionState(profile);
457         return status == BluetoothProfile.STATE_CONNECTED;
458
459     }
460
461     boolean isBusy() {
462         for (LocalBluetoothProfile profile : mProfiles) {
463             int status = getProfileConnectionState(profile);
464             if (status == BluetoothProfile.STATE_CONNECTING
465                     || status == BluetoothProfile.STATE_DISCONNECTING) {
466                 return true;
467             }
468         }
469         return getBondState() == BluetoothDevice.BOND_BONDING;
470     }
471
472     /**
473      * Fetches a new value for the cached BT class.
474      */
475     private void fetchBtClass() {
476         mBtClass = mDevice.getBluetoothClass();
477     }
478
479     private boolean updateProfiles() {
480         ParcelUuid[] uuids = mDevice.getUuids();
481         if (uuids == null) return false;
482
483         ParcelUuid[] localUuids = mLocalAdapter.getUuids();
484         if (localUuids == null) return false;
485
486         mProfileManager.updateProfiles(uuids, localUuids, mProfiles, mRemovedProfiles,
487                                        mLocalNapRoleConnected, mDevice);
488
489         if (DEBUG) {
490             Log.e(TAG, "updating profiles for " + mDevice.getAliasName());
491             BluetoothClass bluetoothClass = mDevice.getBluetoothClass();
492
493             if (bluetoothClass != null) Log.v(TAG, "Class: " + bluetoothClass.toString());
494             Log.v(TAG, "UUID:");
495             for (ParcelUuid uuid : uuids) {
496                 Log.v(TAG, "  " + uuid);
497             }
498         }
499         return true;
500     }
501
502     /**
503      * Refreshes the UI for the BT class, including fetching the latest value
504      * for the class.
505      */
506     void refreshBtClass() {
507         fetchBtClass();
508         dispatchAttributesChanged();
509     }
510
511     /**
512      * Refreshes the UI when framework alerts us of a UUID change.
513      */
514     void onUuidChanged() {
515         updateProfiles();
516
517         if (DEBUG) {
518             Log.e(TAG, "onUuidChanged: Time since last connect"
519                     + (SystemClock.elapsedRealtime() - mConnectAttempted));
520         }
521
522         /*
523          * If a connect was attempted earlier without any UUID, we will do the
524          * connect now.
525          */
526         if (!mProfiles.isEmpty()
527                 && (mConnectAttempted + MAX_UUID_DELAY_FOR_AUTO_CONNECT) > SystemClock
528                         .elapsedRealtime()) {
529             connectWithoutResettingTimer(false);
530         }
531         dispatchAttributesChanged();
532     }
533
534     void onBondingStateChanged(int bondState) {
535         if (bondState == BluetoothDevice.BOND_NONE) {
536             mProfiles.clear();
537             mConnectAfterPairing = false;  // cancel auto-connect
538             setPhonebookPermissionChoice(ACCESS_UNKNOWN);
539             setMessagePermissionChoice(ACCESS_UNKNOWN);
540             mMessageRejectedTimes = 0;
541             saveMessageRejectTimes();
542         }
543
544         refresh();
545
546         if (bondState == BluetoothDevice.BOND_BONDED) {
547             if (mDevice.isBluetoothDock()) {
548                 onBondingDockConnect();
549             } else if (mConnectAfterPairing) {
550                 connect(false);
551             }
552             mConnectAfterPairing = false;
553         }
554     }
555
556     void setBtClass(BluetoothClass btClass) {
557         if (btClass != null && mBtClass != btClass) {
558             mBtClass = btClass;
559             dispatchAttributesChanged();
560         }
561     }
562
563     BluetoothClass getBtClass() {
564         return mBtClass;
565     }
566
567     List<LocalBluetoothProfile> getProfiles() {
568         return Collections.unmodifiableList(mProfiles);
569     }
570
571     List<LocalBluetoothProfile> getConnectableProfiles() {
572         List<LocalBluetoothProfile> connectableProfiles =
573                 new ArrayList<LocalBluetoothProfile>();
574         for (LocalBluetoothProfile profile : mProfiles) {
575             if (profile.isConnectable()) {
576                 connectableProfiles.add(profile);
577             }
578         }
579         return connectableProfiles;
580     }
581
582     List<LocalBluetoothProfile> getRemovedProfiles() {
583         return mRemovedProfiles;
584     }
585
586     void registerCallback(Callback callback) {
587         synchronized (mCallbacks) {
588             mCallbacks.add(callback);
589         }
590     }
591
592     void unregisterCallback(Callback callback) {
593         synchronized (mCallbacks) {
594             mCallbacks.remove(callback);
595         }
596     }
597
598     private void dispatchAttributesChanged() {
599         synchronized (mCallbacks) {
600             for (Callback callback : mCallbacks) {
601                 callback.onDeviceAttributesChanged();
602             }
603         }
604     }
605
606     @Override
607     public String toString() {
608         return mDevice.toString();
609     }
610
611     @Override
612     public boolean equals(Object o) {
613         if ((o == null) || !(o instanceof CachedBluetoothDevice)) {
614             return false;
615         }
616         return mDevice.equals(((CachedBluetoothDevice) o).mDevice);
617     }
618
619     @Override
620     public int hashCode() {
621         return mDevice.getAddress().hashCode();
622     }
623
624     // This comparison uses non-final fields so the sort order may change
625     // when device attributes change (such as bonding state). Settings
626     // will completely refresh the device list when this happens.
627     public int compareTo(CachedBluetoothDevice another) {
628         // Connected above not connected
629         int comparison = (another.isConnected() ? 1 : 0) - (isConnected() ? 1 : 0);
630         if (comparison != 0) return comparison;
631
632         // Paired above not paired
633         comparison = (another.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0) -
634             (getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0);
635         if (comparison != 0) return comparison;
636
637         // Visible above not visible
638         comparison = (another.mVisible ? 1 : 0) - (mVisible ? 1 : 0);
639         if (comparison != 0) return comparison;
640
641         // Stronger signal above weaker signal
642         comparison = another.mRssi - mRssi;
643         if (comparison != 0) return comparison;
644
645         // Fallback on name
646         return mName.compareTo(another.mName);
647     }
648
649     public interface Callback {
650         void onDeviceAttributesChanged();
651     }
652
653     int getPhonebookPermissionChoice() {
654         return mPhonebookPermissionChoice;
655     }
656
657     void setPhonebookPermissionChoice(int permissionChoice) {
658         mPhonebookPermissionChoice = permissionChoice;
659
660         SharedPreferences.Editor editor =
661             mContext.getSharedPreferences(PHONEBOOK_PREFS_NAME, Context.MODE_PRIVATE).edit();
662         if (permissionChoice == ACCESS_UNKNOWN) {
663             editor.remove(mDevice.getAddress());
664         } else {
665             editor.putInt(mDevice.getAddress(), permissionChoice);
666         }
667         editor.commit();
668     }
669
670     private void fetchPhonebookPermissionChoice() {
671         SharedPreferences preference = mContext.getSharedPreferences(PHONEBOOK_PREFS_NAME,
672                                                                      Context.MODE_PRIVATE);
673         mPhonebookPermissionChoice = preference.getInt(mDevice.getAddress(),
674                                                        ACCESS_UNKNOWN);
675     }
676
677     int getMessagePermissionChoice() {
678         return mMessagePermissionChoice;
679     }
680
681     void setMessagePermissionChoice(int permissionChoice) {
682         // if user reject it, only save it when reject exceed limit.
683         if (permissionChoice == ACCESS_REJECTED) {
684             mMessageRejectedTimes++;
685             saveMessageRejectTimes();
686             if (mMessageRejectedTimes < PERSIST_REJECTED_TIMES_LIMIT) {
687                 return;
688             }
689         }
690
691         mMessagePermissionChoice = permissionChoice;
692
693         SharedPreferences.Editor editor =
694             mContext.getSharedPreferences(MESSAGE_PREFS_NAME, Context.MODE_PRIVATE).edit();
695         if (permissionChoice == ACCESS_UNKNOWN) {
696             editor.remove(mDevice.getAddress());
697         } else {
698             editor.putInt(mDevice.getAddress(), permissionChoice);
699         }
700         editor.commit();
701     }
702
703     private void fetchMessagePermissionChoice() {
704         SharedPreferences preference = mContext.getSharedPreferences(MESSAGE_PREFS_NAME,
705                                                                      Context.MODE_PRIVATE);
706         mMessagePermissionChoice = preference.getInt(mDevice.getAddress(),
707                                                        ACCESS_UNKNOWN);
708     }
709
710     private void fetchMessageRejectTimes() {
711         SharedPreferences preference = mContext.getSharedPreferences(MESSAGE_REJECT_TIMES,
712                                                                      Context.MODE_PRIVATE);
713         mMessageRejectedTimes = preference.getInt(mDevice.getAddress(), 0);
714     }
715
716     private void saveMessageRejectTimes() {
717         SharedPreferences.Editor editor =
718             mContext.getSharedPreferences(MESSAGE_REJECT_TIMES, Context.MODE_PRIVATE).edit();
719         if (mMessageRejectedTimes == 0) {
720             editor.remove(mDevice.getAddress());
721         } else {
722             editor.putInt(mDevice.getAddress(), mMessageRejectedTimes);
723         }
724         editor.commit();
725     }
726
727 }