• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.applications;
18 
19 import android.app.ActivityManager;
20 import android.app.AlertDialog;
21 import android.app.AppGlobals;
22 import android.app.LoaderManager;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.Intent;
26 import android.content.Loader;
27 import android.content.UriPermission;
28 import android.content.pm.ApplicationInfo;
29 import android.content.pm.IPackageDataObserver;
30 import android.content.pm.PackageManager;
31 import android.content.pm.ProviderInfo;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.os.Message;
35 import android.os.RemoteException;
36 import android.os.UserHandle;
37 import android.os.storage.StorageManager;
38 import android.os.storage.VolumeInfo;
39 import android.support.v7.preference.Preference;
40 import android.support.v7.preference.PreferenceCategory;
41 import android.util.Log;
42 import android.util.MutableInt;
43 import android.view.View;
44 import android.view.View.OnClickListener;
45 import android.widget.Button;
46 
47 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
48 import com.android.settings.R;
49 import com.android.settings.Utils;
50 import com.android.settings.deviceinfo.StorageWizardMoveConfirm;
51 import com.android.settingslib.RestrictedLockUtils;
52 import com.android.settingslib.applications.ApplicationsState.Callbacks;
53 import com.android.settingslib.applications.StorageStatsSource;
54 import com.android.settingslib.applications.StorageStatsSource.AppStorageStats;
55 
56 import java.util.Collections;
57 import java.util.List;
58 import java.util.Map;
59 import java.util.Objects;
60 import java.util.TreeMap;
61 
62 import static android.content.pm.ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA;
63 import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
64 
65 public class AppStorageSettings extends AppInfoWithHeader
66         implements OnClickListener, Callbacks, DialogInterface.OnClickListener,
67         LoaderManager.LoaderCallbacks<AppStorageStats> {
68     private static final String TAG = AppStorageSettings.class.getSimpleName();
69 
70     //internal constants used in Handler
71     private static final int OP_SUCCESSFUL = 1;
72     private static final int OP_FAILED = 2;
73     private static final int MSG_CLEAR_USER_DATA = 1;
74     private static final int MSG_CLEAR_CACHE = 3;
75 
76     // invalid size value used initially and also when size retrieval through PackageManager
77     // fails for whatever reason
78     private static final int SIZE_INVALID = -1;
79 
80     // Result code identifiers
81     public static final int REQUEST_MANAGE_SPACE = 2;
82 
83     private static final int DLG_CLEAR_DATA = DLG_BASE + 1;
84     private static final int DLG_CANNOT_CLEAR_DATA = DLG_BASE + 2;
85 
86     private static final String KEY_STORAGE_USED = "storage_used";
87     private static final String KEY_CHANGE_STORAGE = "change_storage_button";
88     private static final String KEY_STORAGE_SPACE = "storage_space";
89     private static final String KEY_STORAGE_CATEGORY = "storage_category";
90 
91     private static final String KEY_TOTAL_SIZE = "total_size";
92     private static final String KEY_APP_SIZE = "app_size";
93     private static final String KEY_DATA_SIZE = "data_size";
94     private static final String KEY_CACHE_SIZE = "cache_size";
95 
96     private static final String KEY_HEADER_BUTTONS = "header_view";
97 
98     private static final String KEY_URI_CATEGORY = "uri_category";
99     private static final String KEY_CLEAR_URI = "clear_uri_button";
100 
101     private static final String KEY_CACHE_CLEARED = "cache_cleared";
102     private static final String KEY_DATA_CLEARED = "data_cleared";
103 
104     // Views related to cache info
105     private Preference mCacheSize;
106     private Button mClearDataButton;
107     private Button mClearCacheButton;
108 
109     private Preference mStorageUsed;
110     private Button mChangeStorageButton;
111 
112     // Views related to URI permissions
113     private Button mClearUriButton;
114     private LayoutPreference mClearUri;
115     private PreferenceCategory mUri;
116 
117     private boolean mCanClearData = true;
118     private boolean mCacheCleared;
119     private boolean mDataCleared;
120 
121     private AppStorageSizesController mSizeController;
122 
123     private ClearCacheObserver mClearCacheObserver;
124     private ClearUserDataObserver mClearDataObserver;
125 
126     private VolumeInfo[] mCandidates;
127     private AlertDialog.Builder mDialogBuilder;
128     private ApplicationInfo mInfo;
129 
130     @Override
onCreate(Bundle savedInstanceState)131     public void onCreate(Bundle savedInstanceState) {
132         super.onCreate(savedInstanceState);
133         if (savedInstanceState != null) {
134             mCacheCleared = savedInstanceState.getBoolean(KEY_CACHE_CLEARED, false);
135             mDataCleared = savedInstanceState.getBoolean(KEY_DATA_CLEARED, false);
136             mCacheCleared = mCacheCleared || mDataCleared;
137         }
138 
139         addPreferencesFromResource(R.xml.app_storage_settings);
140         setupViews();
141         initMoveDialog();
142     }
143 
144     @Override
onResume()145     public void onResume() {
146         super.onResume();
147         updateSize();
148     }
149 
150     @Override
onSaveInstanceState(Bundle outState)151     public void onSaveInstanceState(Bundle outState) {
152         super.onSaveInstanceState(outState);
153         outState.putBoolean(KEY_CACHE_CLEARED, mCacheCleared);
154         outState.putBoolean(KEY_DATA_CLEARED, mDataCleared);
155     }
156 
setupViews()157     private void setupViews() {
158         // Set default values on sizes
159         mSizeController = new AppStorageSizesController.Builder()
160                 .setTotalSizePreference(findPreference(KEY_TOTAL_SIZE))
161                 .setAppSizePreference(findPreference(KEY_APP_SIZE))
162                 .setDataSizePreference(findPreference(KEY_DATA_SIZE))
163                 .setCacheSizePreference(findPreference(KEY_CACHE_SIZE))
164                 .setComputingString(R.string.computing_size)
165                 .setErrorString(R.string.invalid_size_value)
166                 .build();
167 
168         mClearDataButton = (Button) ((LayoutPreference) findPreference(KEY_HEADER_BUTTONS))
169                 .findViewById(R.id.left_button);
170 
171         mStorageUsed = findPreference(KEY_STORAGE_USED);
172         mChangeStorageButton = (Button) ((LayoutPreference) findPreference(KEY_CHANGE_STORAGE))
173                 .findViewById(R.id.button);
174         mChangeStorageButton.setText(R.string.change);
175         mChangeStorageButton.setOnClickListener(this);
176 
177         // Cache section
178         mCacheSize = findPreference(KEY_CACHE_SIZE);
179         mClearCacheButton = (Button) ((LayoutPreference) findPreference(KEY_HEADER_BUTTONS))
180                 .findViewById(R.id.right_button);
181         mClearCacheButton.setText(R.string.clear_cache_btn_text);
182 
183         // URI permissions section
184         mUri = (PreferenceCategory) findPreference(KEY_URI_CATEGORY);
185         mClearUri = (LayoutPreference) mUri.findPreference(KEY_CLEAR_URI);
186         mClearUriButton = (Button) mClearUri.findViewById(R.id.button);
187         mClearUriButton.setText(R.string.clear_uri_btn_text);
188         mClearUriButton.setOnClickListener(this);
189     }
190 
191     @Override
onClick(View v)192     public void onClick(View v) {
193         if (v == mClearCacheButton) {
194             if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) {
195                 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
196                         getActivity(), mAppsControlDisallowedAdmin);
197                 return;
198             } else if (mClearCacheObserver == null) { // Lazy initialization of observer
199                 mClearCacheObserver = new ClearCacheObserver();
200             }
201             mMetricsFeatureProvider.action(getContext(),
202                     MetricsEvent.ACTION_SETTINGS_CLEAR_APP_CACHE);
203             mPm.deleteApplicationCacheFiles(mPackageName, mClearCacheObserver);
204         } else if (v == mClearDataButton) {
205             if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) {
206                 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
207                         getActivity(), mAppsControlDisallowedAdmin);
208             } else if (mAppEntry.info.manageSpaceActivityName != null) {
209                 if (!Utils.isMonkeyRunning()) {
210                     Intent intent = new Intent(Intent.ACTION_DEFAULT);
211                     intent.setClassName(mAppEntry.info.packageName,
212                             mAppEntry.info.manageSpaceActivityName);
213                     startActivityForResult(intent, REQUEST_MANAGE_SPACE);
214                 }
215             } else {
216                 showDialogInner(DLG_CLEAR_DATA, 0);
217             }
218         } else if (v == mChangeStorageButton && mDialogBuilder != null && !isMoveInProgress()) {
219             mDialogBuilder.show();
220         } else if (v == mClearUriButton) {
221             if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) {
222                 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
223                         getActivity(), mAppsControlDisallowedAdmin);
224             } else {
225                 clearUriPermissions();
226             }
227         }
228     }
229 
isMoveInProgress()230     private boolean isMoveInProgress() {
231         try {
232             // TODO: define a cleaner API for this
233             AppGlobals.getPackageManager().checkPackageStartable(mPackageName,
234                     UserHandle.myUserId());
235             return false;
236         } catch (RemoteException | SecurityException e) {
237             return true;
238         }
239     }
240 
241     @Override
onClick(DialogInterface dialog, int which)242     public void onClick(DialogInterface dialog, int which) {
243         final Context context = getActivity();
244 
245         // If not current volume, kick off move wizard
246         final VolumeInfo targetVol = mCandidates[which];
247         final VolumeInfo currentVol = context.getPackageManager().getPackageCurrentVolume(
248                 mAppEntry.info);
249         if (!Objects.equals(targetVol, currentVol)) {
250             final Intent intent = new Intent(context, StorageWizardMoveConfirm.class);
251             intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, targetVol.getId());
252             intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mAppEntry.info.packageName);
253             startActivity(intent);
254         }
255         dialog.dismiss();
256     }
257 
258     @Override
refreshUi()259     protected boolean refreshUi() {
260         retrieveAppEntry();
261         if (mAppEntry == null) {
262             return false;
263         }
264         updateUiWithSize(mSizeController.getLastResult());
265         refreshGrantedUriPermissions();
266 
267         final VolumeInfo currentVol = getActivity().getPackageManager()
268                 .getPackageCurrentVolume(mAppEntry.info);
269         final StorageManager storage = getContext().getSystemService(StorageManager.class);
270         mStorageUsed.setSummary(storage.getBestVolumeDescription(currentVol));
271 
272         refreshButtons();
273 
274         return true;
275     }
276 
refreshButtons()277     private void refreshButtons() {
278         initMoveDialog();
279         initDataButtons();
280     }
281 
initDataButtons()282     private void initDataButtons() {
283         final boolean appHasSpaceManagementUI = mAppEntry.info.manageSpaceActivityName != null;
284         final boolean appHasActiveAdmins = mDpm.packageHasActiveAdmins(mPackageName);
285         // Check that SYSTEM_APP flag is set, and ALLOW_CLEAR_USER_DATA is not set.
286         final boolean isNonClearableSystemApp =
287                 (mAppEntry.info.flags & (FLAG_SYSTEM | FLAG_ALLOW_CLEAR_USER_DATA)) == FLAG_SYSTEM;
288         final boolean appRestrictsClearingData = isNonClearableSystemApp || appHasActiveAdmins;
289 
290         final Intent intent = new Intent(Intent.ACTION_DEFAULT);
291         if (appHasSpaceManagementUI) {
292             intent.setClassName(mAppEntry.info.packageName, mAppEntry.info.manageSpaceActivityName);
293         }
294         final boolean isManageSpaceActivityAvailable =
295                 getPackageManager().resolveActivity(intent, 0) != null;
296 
297         if ((!appHasSpaceManagementUI && appRestrictsClearingData)
298                 || !isManageSpaceActivityAvailable) {
299             mClearDataButton.setText(R.string.clear_user_data_text);
300             mClearDataButton.setEnabled(false);
301             mCanClearData = false;
302         } else {
303             if (appHasSpaceManagementUI) {
304                 mClearDataButton.setText(R.string.manage_space_text);
305             } else {
306                 mClearDataButton.setText(R.string.clear_user_data_text);
307             }
308             mClearDataButton.setOnClickListener(this);
309         }
310 
311         if (mAppsControlDisallowedBySystem) {
312             mClearDataButton.setEnabled(false);
313         }
314     }
315 
initMoveDialog()316     private void initMoveDialog() {
317         final Context context = getActivity();
318         final StorageManager storage = context.getSystemService(StorageManager.class);
319 
320         final List<VolumeInfo> candidates = context.getPackageManager()
321                 .getPackageCandidateVolumes(mAppEntry.info);
322         if (candidates.size() > 1) {
323             Collections.sort(candidates, VolumeInfo.getDescriptionComparator());
324 
325             CharSequence[] labels = new CharSequence[candidates.size()];
326             int current = -1;
327             for (int i = 0; i < candidates.size(); i++) {
328                 final String volDescrip = storage.getBestVolumeDescription(candidates.get(i));
329                 if (Objects.equals(volDescrip, mStorageUsed.getSummary())) {
330                     current = i;
331                 }
332                 labels[i] = volDescrip;
333             }
334             mCandidates = candidates.toArray(new VolumeInfo[candidates.size()]);
335             mDialogBuilder = new AlertDialog.Builder(getContext())
336                     .setTitle(R.string.change_storage)
337                     .setSingleChoiceItems(labels, current, this)
338                     .setNegativeButton(R.string.cancel, null);
339         } else {
340             removePreference(KEY_STORAGE_USED);
341             removePreference(KEY_CHANGE_STORAGE);
342             removePreference(KEY_STORAGE_SPACE);
343         }
344     }
345 
346     /*
347      * Private method to initiate clearing user data when the user clicks the clear data
348      * button for a system package
349      */
initiateClearUserData()350     private void initiateClearUserData() {
351         mMetricsFeatureProvider.action(getContext(), MetricsEvent.ACTION_SETTINGS_CLEAR_APP_DATA);
352         mClearDataButton.setEnabled(false);
353         // Invoke uninstall or clear user data based on sysPackage
354         String packageName = mAppEntry.info.packageName;
355         Log.i(TAG, "Clearing user data for package : " + packageName);
356         if (mClearDataObserver == null) {
357             mClearDataObserver = new ClearUserDataObserver();
358         }
359         ActivityManager am = (ActivityManager)
360                 getActivity().getSystemService(Context.ACTIVITY_SERVICE);
361         boolean res = am.clearApplicationUserData(packageName, mClearDataObserver);
362         if (!res) {
363             // Clearing data failed for some obscure reason. Just log error for now
364             Log.i(TAG, "Couldnt clear application user data for package:"+packageName);
365             showDialogInner(DLG_CANNOT_CLEAR_DATA, 0);
366         } else {
367             mClearDataButton.setText(R.string.recompute_size);
368         }
369     }
370 
371     /*
372      * Private method to handle clear message notification from observer when
373      * the async operation from PackageManager is complete
374      */
processClearMsg(Message msg)375     private void processClearMsg(Message msg) {
376         int result = msg.arg1;
377         String packageName = mAppEntry.info.packageName;
378         mClearDataButton.setText(R.string.clear_user_data_text);
379         if (result == OP_SUCCESSFUL) {
380             Log.i(TAG, "Cleared user data for package : "+packageName);
381             updateSize();
382         } else {
383             mClearDataButton.setEnabled(true);
384         }
385     }
386 
refreshGrantedUriPermissions()387     private void refreshGrantedUriPermissions() {
388         // Clear UI first (in case the activity has been resumed)
389         removeUriPermissionsFromUi();
390 
391         // Gets all URI permissions from am.
392         ActivityManager am = (ActivityManager) getActivity().getSystemService(
393                 Context.ACTIVITY_SERVICE);
394         List<UriPermission> perms =
395                 am.getGrantedUriPermissions(mAppEntry.info.packageName).getList();
396 
397         if (perms.isEmpty()) {
398             mClearUriButton.setVisibility(View.GONE);
399             return;
400         }
401 
402         PackageManager pm = getActivity().getPackageManager();
403 
404         // Group number of URIs by app.
405         Map<CharSequence, MutableInt> uriCounters = new TreeMap<>();
406         for (UriPermission perm : perms) {
407             String authority = perm.getUri().getAuthority();
408             ProviderInfo provider = pm.resolveContentProvider(authority, 0);
409             CharSequence app = provider.applicationInfo.loadLabel(pm);
410             MutableInt count = uriCounters.get(app);
411             if (count == null) {
412                 uriCounters.put(app, new MutableInt(1));
413             } else {
414                 count.value++;
415             }
416         }
417 
418         // Dynamically add the preferences, one per app.
419         int order = 0;
420         for (Map.Entry<CharSequence, MutableInt> entry : uriCounters.entrySet()) {
421             int numberResources = entry.getValue().value;
422             Preference pref = new Preference(getPrefContext());
423             pref.setTitle(entry.getKey());
424             pref.setSummary(getPrefContext().getResources()
425                     .getQuantityString(R.plurals.uri_permissions_text, numberResources,
426                             numberResources));
427             pref.setSelectable(false);
428             pref.setLayoutResource(R.layout.horizontal_preference);
429             pref.setOrder(order);
430             Log.v(TAG, "Adding preference '" + pref + "' at order " + order);
431             mUri.addPreference(pref);
432         }
433 
434         if (mAppsControlDisallowedBySystem) {
435             mClearUriButton.setEnabled(false);
436         }
437 
438         mClearUri.setOrder(order);
439         mClearUriButton.setVisibility(View.VISIBLE);
440 
441     }
442 
clearUriPermissions()443     private void clearUriPermissions() {
444         // Synchronously revoke the permissions.
445         final ActivityManager am = (ActivityManager) getActivity().getSystemService(
446                 Context.ACTIVITY_SERVICE);
447         am.clearGrantedUriPermissions(mAppEntry.info.packageName);
448 
449         // Update UI
450         refreshGrantedUriPermissions();
451     }
452 
removeUriPermissionsFromUi()453     private void removeUriPermissionsFromUi() {
454         // Remove all preferences but the clear button.
455         int count = mUri.getPreferenceCount();
456         for (int i = count - 1; i >= 0; i--) {
457             Preference pref = mUri.getPreference(i);
458             if (pref != mClearUri) {
459                 mUri.removePreference(pref);
460             }
461         }
462     }
463 
464     @Override
createDialog(int id, int errorCode)465     protected AlertDialog createDialog(int id, int errorCode) {
466         switch (id) {
467             case DLG_CLEAR_DATA:
468                 return new AlertDialog.Builder(getActivity())
469                         .setTitle(getActivity().getText(R.string.clear_data_dlg_title))
470                         .setMessage(getActivity().getText(R.string.clear_data_dlg_text))
471                         .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
472                             public void onClick(DialogInterface dialog, int which) {
473                                 // Clear user data here
474                                 initiateClearUserData();
475                             }
476                         })
477                         .setNegativeButton(R.string.dlg_cancel, null)
478                         .create();
479             case DLG_CANNOT_CLEAR_DATA:
480                 return new AlertDialog.Builder(getActivity())
481                         .setTitle(getActivity().getText(R.string.clear_failed_dlg_title))
482                         .setMessage(getActivity().getText(R.string.clear_failed_dlg_text))
483                         .setNeutralButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
484                             public void onClick(DialogInterface dialog, int which) {
485                                 mClearDataButton.setEnabled(false);
486                                 //force to recompute changed value
487                                 setIntentAndFinish(false, false);
488                             }
489                         })
490                         .create();
491         }
492         return null;
493     }
494 
495     @Override
496     public void onPackageSizeChanged(String packageName) {
497     }
498 
499     @Override
500     public Loader<AppStorageStats> onCreateLoader(int id, Bundle args) {
501         Context context = getContext();
502         return new FetchPackageStorageAsyncLoader(
503                 context, new StorageStatsSource(context), mInfo, UserHandle.of(mUserId));
504     }
505 
506     @Override
507     public void onLoadFinished(Loader<AppStorageStats> loader, AppStorageStats result) {
508         mSizeController.setResult(result);
509         updateUiWithSize(result);
510     }
511 
512     @Override
513     public void onLoaderReset(Loader<AppStorageStats> loader) {
514     }
515 
516     private void updateSize() {
517         PackageManager packageManager = getPackageManager();
518         try {
519             mInfo = packageManager.getApplicationInfo(mPackageName, 0);
520         } catch (PackageManager.NameNotFoundException e) {
521             Log.e(TAG, "Could not find package", e);
522         }
523 
524         if (mInfo == null) {
525             return;
526         }
527 
528         getLoaderManager().restartLoader(1, Bundle.EMPTY, this);
529     }
530 
531     private void updateUiWithSize(AppStorageStats result) {
532         if (mCacheCleared) {
533             mSizeController.setCacheCleared(true);
534         }
535         if (mDataCleared) {
536             mSizeController.setDataCleared(true);
537         }
538 
539         mSizeController.updateUi(getContext());
540 
541         if (result == null) {
542             mClearDataButton.setEnabled(false);
543             mClearCacheButton.setEnabled(false);
544         } else {
545             long codeSize = result.getCodeBytes();
546             long cacheSize = result.getCacheBytes();
547             long dataSize = result.getDataBytes() - cacheSize;
548 
549             if (dataSize <= 0 || !mCanClearData || mDataCleared) {
550                 mClearDataButton.setEnabled(false);
551             } else {
552                 mClearDataButton.setEnabled(true);
553                 mClearDataButton.setOnClickListener(this);
554             }
555             if (cacheSize <= 0 || mCacheCleared) {
556                 mClearCacheButton.setEnabled(false);
557             } else {
558                 mClearCacheButton.setEnabled(true);
559                 mClearCacheButton.setOnClickListener(this);
560             }
561         }
562         if (mAppsControlDisallowedBySystem) {
563             mClearCacheButton.setEnabled(false);
564             mClearDataButton.setEnabled(false);
565         }
566     }
567 
568     private final Handler mHandler = new Handler() {
569         public void handleMessage(Message msg) {
570             if (getView() == null) {
571                 return;
572             }
573             switch (msg.what) {
574                 case MSG_CLEAR_USER_DATA:
575                     mDataCleared = true;
576                     mCacheCleared = true;
577                     processClearMsg(msg);
578                     break;
579                 case MSG_CLEAR_CACHE:
580                     mCacheCleared = true;
581                     // Refresh size info
582                     updateSize();
583                     break;
584             }
585         }
586     };
587 
588     @Override
589     public int getMetricsCategory() {
590         return MetricsEvent.APPLICATIONS_APP_STORAGE;
591     }
592 
593     class ClearCacheObserver extends IPackageDataObserver.Stub {
594         public void onRemoveCompleted(final String packageName, final boolean succeeded) {
595             final Message msg = mHandler.obtainMessage(MSG_CLEAR_CACHE);
596             msg.arg1 = succeeded ? OP_SUCCESSFUL : OP_FAILED;
597             mHandler.sendMessage(msg);
598         }
599     }
600 
601     class ClearUserDataObserver extends IPackageDataObserver.Stub {
602        public void onRemoveCompleted(final String packageName, final boolean succeeded) {
603            final Message msg = mHandler.obtainMessage(MSG_CLEAR_USER_DATA);
604            msg.arg1 = succeeded ? OP_SUCCESSFUL : OP_FAILED;
605            mHandler.sendMessage(msg);
606         }
607     }
608 }
609