Home | History | Annotate | Download | only in applications
      1 package com.android.settings.applications;
      2 
      3 import com.android.settings.R;
      4 
      5 import android.app.Activity;
      6 import android.app.ActivityManager;
      7 import android.app.AlertDialog;
      8 import android.app.ApplicationErrorReport;
      9 import android.app.Dialog;
     10 import android.app.DialogFragment;
     11 import android.app.Fragment;
     12 import android.app.PendingIntent;
     13 import android.content.ActivityNotFoundException;
     14 import android.content.ComponentName;
     15 import android.content.Context;
     16 import android.content.DialogInterface;
     17 import android.content.Intent;
     18 import android.content.IntentSender;
     19 import android.content.pm.ApplicationInfo;
     20 import android.content.pm.PackageManager;
     21 import android.content.pm.ProviderInfo;
     22 import android.content.pm.ServiceInfo;
     23 import android.content.pm.PackageManager.NameNotFoundException;
     24 import android.content.res.Resources;
     25 import android.os.Bundle;
     26 import android.os.Debug;
     27 import android.os.Handler;
     28 import android.os.SystemClock;
     29 import android.provider.Settings;
     30 import android.util.Log;
     31 import android.view.LayoutInflater;
     32 import android.view.View;
     33 import android.view.ViewGroup;
     34 import android.widget.Button;
     35 import android.widget.TextView;
     36 
     37 import java.io.File;
     38 import java.io.FileInputStream;
     39 import java.io.FileOutputStream;
     40 import java.io.IOException;
     41 import java.util.ArrayList;
     42 
     43 public class RunningServiceDetails extends Fragment
     44         implements RunningState.OnRefreshUiListener {
     45     static final String TAG = "RunningServicesDetails";
     46 
     47     static final String KEY_UID = "uid";
     48     static final String KEY_PROCESS = "process";
     49     static final String KEY_BACKGROUND = "background";
     50 
     51     static final int DIALOG_CONFIRM_STOP = 1;
     52 
     53     ActivityManager mAm;
     54     LayoutInflater mInflater;
     55 
     56     RunningState mState;
     57     boolean mHaveData;
     58 
     59     int mUid;
     60     String mProcessName;
     61     boolean mShowBackground;
     62 
     63     RunningState.MergedItem mMergedItem;
     64 
     65     View mRootView;
     66     ViewGroup mAllDetails;
     67     ViewGroup mSnippet;
     68     RunningProcessesView.ActiveItem mSnippetActiveItem;
     69     RunningProcessesView.ViewHolder mSnippetViewHolder;
     70 
     71     int mNumServices, mNumProcesses;
     72 
     73     TextView mServicesHeader;
     74     TextView mProcessesHeader;
     75     final ArrayList<ActiveDetail> mActiveDetails = new ArrayList<ActiveDetail>();
     76 
     77     class ActiveDetail implements View.OnClickListener {
     78         View mRootView;
     79         Button mStopButton;
     80         Button mReportButton;
     81         RunningState.ServiceItem mServiceItem;
     82         RunningProcessesView.ActiveItem mActiveItem;
     83         RunningProcessesView.ViewHolder mViewHolder;
     84         PendingIntent mManageIntent;
     85         ComponentName mInstaller;
     86 
     87         void stopActiveService(boolean confirmed) {
     88             RunningState.ServiceItem si = mServiceItem;
     89             if (!confirmed) {
     90                 if ((si.mServiceInfo.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
     91                     showConfirmStopDialog(si.mRunningService.service);
     92                     return;
     93                 }
     94             }
     95             getActivity().stopService(new Intent().setComponent(si.mRunningService.service));
     96             if (mMergedItem == null) {
     97                 // If this is gone, we are gone.
     98                 mState.updateNow();
     99                 finish();
    100             } else if (!mShowBackground && mMergedItem.mServices.size() <= 1) {
    101                 // If there was only one service, we are finishing it,
    102                 // so no reason for the UI to stick around.
    103                 mState.updateNow();
    104                 finish();
    105             } else {
    106                 mState.updateNow();
    107             }
    108         }
    109 
    110         public void onClick(View v) {
    111             if (v == mReportButton) {
    112                 ApplicationErrorReport report = new ApplicationErrorReport();
    113                 report.type = ApplicationErrorReport.TYPE_RUNNING_SERVICE;
    114                 report.packageName = mServiceItem.mServiceInfo.packageName;
    115                 report.installerPackageName = mInstaller.getPackageName();
    116                 report.processName = mServiceItem.mRunningService.process;
    117                 report.time = System.currentTimeMillis();
    118                 report.systemApp = (mServiceItem.mServiceInfo.applicationInfo.flags
    119                         & ApplicationInfo.FLAG_SYSTEM) != 0;
    120                 ApplicationErrorReport.RunningServiceInfo info
    121                         = new ApplicationErrorReport.RunningServiceInfo();
    122                 if (mActiveItem.mFirstRunTime >= 0) {
    123                     info.durationMillis = SystemClock.elapsedRealtime()-mActiveItem.mFirstRunTime;
    124                 } else {
    125                     info.durationMillis = -1;
    126                 }
    127                 ComponentName comp = new ComponentName(mServiceItem.mServiceInfo.packageName,
    128                         mServiceItem.mServiceInfo.name);
    129                 File filename = getActivity().getFileStreamPath("service_dump.txt");
    130                 FileOutputStream output = null;
    131                 try {
    132                     output = new FileOutputStream(filename);
    133                     Debug.dumpService("activity", output.getFD(),
    134                             new String[] { "-a", "service", comp.flattenToString() });
    135                 } catch (IOException e) {
    136                     Log.w(TAG, "Can't dump service: " + comp, e);
    137                 } finally {
    138                     if (output != null) try { output.close(); } catch (IOException e) {}
    139                 }
    140                 FileInputStream input = null;
    141                 try {
    142                     input = new FileInputStream(filename);
    143                     byte[] buffer = new byte[(int) filename.length()];
    144                     input.read(buffer);
    145                     info.serviceDetails = new String(buffer);
    146                 } catch (IOException e) {
    147                     Log.w(TAG, "Can't read service dump: " + comp, e);
    148                 } finally {
    149                     if (input != null) try { input.close(); } catch (IOException e) {}
    150                 }
    151                 filename.delete();
    152                 Log.i(TAG, "Details: " + info.serviceDetails);
    153                 report.runningServiceInfo = info;
    154                 Intent result = new Intent(Intent.ACTION_APP_ERROR);
    155                 result.setComponent(mInstaller);
    156                 result.putExtra(Intent.EXTRA_BUG_REPORT, report);
    157                 result.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    158                 startActivity(result);
    159                 return;
    160             }
    161 
    162             if (mManageIntent != null) {
    163                 try {
    164                     getActivity().startIntentSender(mManageIntent.getIntentSender(), null,
    165                             Intent.FLAG_ACTIVITY_NEW_TASK
    166                                     | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET,
    167                             Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET, 0);
    168                 } catch (IntentSender.SendIntentException e) {
    169                     Log.w(TAG, e);
    170                 } catch (IllegalArgumentException e) {
    171                     Log.w(TAG, e);
    172                 } catch (ActivityNotFoundException e) {
    173                     Log.w(TAG, e);
    174                 }
    175             } else if (mServiceItem != null) {
    176                 stopActiveService(false);
    177             } else if (mActiveItem.mItem.mBackground) {
    178                 // Background process.  Just kill it.
    179                 mAm.killBackgroundProcesses(mActiveItem.mItem.mPackageInfo.packageName);
    180                 finish();
    181             } else {
    182                 // Heavy-weight process.  We'll do a force-stop on it.
    183                 mAm.forceStopPackage(mActiveItem.mItem.mPackageInfo.packageName);
    184                 finish();
    185             }
    186         }
    187     }
    188 
    189     StringBuilder mBuilder = new StringBuilder(128);
    190 
    191     boolean findMergedItem() {
    192         RunningState.MergedItem item = null;
    193         ArrayList<RunningState.MergedItem> newItems = mShowBackground
    194                 ? mState.getCurrentBackgroundItems() : mState.getCurrentMergedItems();
    195         if (newItems != null) {
    196             for (int i=0; i<newItems.size(); i++) {
    197                 RunningState.MergedItem mi = newItems.get(i);
    198                 if (mi.mProcess.mUid == mUid
    199                         && mi.mProcess.mProcessName.equals(mProcessName)) {
    200                     item = mi;
    201                     break;
    202                 }
    203             }
    204         }
    205 
    206         if (mMergedItem != item) {
    207             mMergedItem = item;
    208             return true;
    209         }
    210         return false;
    211     }
    212 
    213     void addServiceDetailsView(RunningState.ServiceItem si, RunningState.MergedItem mi) {
    214         if (mNumServices == 0) {
    215             mServicesHeader = (TextView)mInflater.inflate(R.layout.separator_label,
    216                     mAllDetails, false);
    217             mServicesHeader.setText(R.string.runningservicedetails_services_title);
    218             mAllDetails.addView(mServicesHeader);
    219         }
    220         mNumServices++;
    221 
    222         RunningState.BaseItem bi = si != null ? si : mi;
    223 
    224         ActiveDetail detail = new ActiveDetail();
    225         View root = mInflater.inflate(R.layout.running_service_details_service,
    226                 mAllDetails, false);
    227         mAllDetails.addView(root);
    228         detail.mRootView = root;
    229         detail.mServiceItem = si;
    230         detail.mViewHolder = new RunningProcessesView.ViewHolder(root);
    231         detail.mActiveItem = detail.mViewHolder.bind(mState, bi, mBuilder);
    232 
    233         if (si != null && si.mRunningService.clientLabel != 0) {
    234             detail.mManageIntent = mAm.getRunningServiceControlPanel(
    235                     si.mRunningService.service);
    236         }
    237 
    238         TextView description = (TextView)root.findViewById(R.id.comp_description);
    239         if (si != null && si.mServiceInfo.descriptionRes != 0) {
    240             description.setText(getActivity().getPackageManager().getText(
    241                     si.mServiceInfo.packageName, si.mServiceInfo.descriptionRes,
    242                     si.mServiceInfo.applicationInfo));
    243         } else {
    244             if (mi.mBackground) {
    245                 description.setText(R.string.background_process_stop_description);
    246             } else if (detail.mManageIntent != null) {
    247                 try {
    248                     Resources clientr = getActivity().getPackageManager().getResourcesForApplication(
    249                             si.mRunningService.clientPackage);
    250                     String label = clientr.getString(si.mRunningService.clientLabel);
    251                     description.setText(getActivity().getString(R.string.service_manage_description,
    252                             label));
    253                 } catch (PackageManager.NameNotFoundException e) {
    254                 }
    255             } else {
    256                 description.setText(getActivity().getText(si != null
    257                         ? R.string.service_stop_description
    258                         : R.string.heavy_weight_stop_description));
    259             }
    260         }
    261 
    262         detail.mStopButton = (Button)root.findViewById(R.id.left_button);
    263         detail.mStopButton.setOnClickListener(detail);
    264         detail.mStopButton.setText(getActivity().getText(detail.mManageIntent != null
    265                 ? R.string.service_manage : R.string.service_stop));
    266 
    267         detail.mReportButton = (Button)root.findViewById(R.id.right_button);
    268         detail.mReportButton.setOnClickListener(detail);
    269         detail.mReportButton.setText(com.android.internal.R.string.report);
    270         // check if error reporting is enabled in secure settings
    271         int enabled = Settings.Secure.getInt(getActivity().getContentResolver(),
    272                 Settings.Secure.SEND_ACTION_APP_ERROR, 0);
    273         if (enabled != 0 && si != null) {
    274             detail.mInstaller = ApplicationErrorReport.getErrorReportReceiver(
    275                     getActivity(), si.mServiceInfo.packageName,
    276                     si.mServiceInfo.applicationInfo.flags);
    277             detail.mReportButton.setEnabled(detail.mInstaller != null);
    278         } else {
    279             detail.mReportButton.setEnabled(false);
    280         }
    281 
    282         mActiveDetails.add(detail);
    283     }
    284 
    285     void addProcessDetailsView(RunningState.ProcessItem pi, boolean isMain) {
    286         if (mNumProcesses == 0) {
    287             mProcessesHeader = (TextView)mInflater.inflate(R.layout.separator_label,
    288                     mAllDetails, false);
    289             mProcessesHeader.setText(R.string.runningservicedetails_processes_title);
    290             mAllDetails.addView(mProcessesHeader);
    291         }
    292         mNumProcesses++;
    293 
    294         ActiveDetail detail = new ActiveDetail();
    295         View root = mInflater.inflate(R.layout.running_service_details_process,
    296                 mAllDetails, false);
    297         mAllDetails.addView(root);
    298         detail.mRootView = root;
    299         detail.mViewHolder = new RunningProcessesView.ViewHolder(root);
    300         detail.mActiveItem = detail.mViewHolder.bind(mState, pi, mBuilder);
    301 
    302         TextView description = (TextView)root.findViewById(R.id.comp_description);
    303         if (isMain) {
    304             description.setText(R.string.main_running_process_description);
    305         } else {
    306             int textid = 0;
    307             CharSequence label = null;
    308             ActivityManager.RunningAppProcessInfo rpi = pi.mRunningProcessInfo;
    309             final ComponentName comp = rpi.importanceReasonComponent;
    310             //Log.i(TAG, "Secondary proc: code=" + rpi.importanceReasonCode
    311             //        + " pid=" + rpi.importanceReasonPid + " comp=" + comp);
    312             switch (rpi.importanceReasonCode) {
    313                 case ActivityManager.RunningAppProcessInfo.REASON_PROVIDER_IN_USE:
    314                     textid = R.string.process_provider_in_use_description;
    315                     if (rpi.importanceReasonComponent != null) {
    316                         try {
    317                             ProviderInfo prov = getActivity().getPackageManager().getProviderInfo(
    318                                     rpi.importanceReasonComponent, 0);
    319                             label = RunningState.makeLabel(getActivity().getPackageManager(),
    320                                     prov.name, prov);
    321                         } catch (NameNotFoundException e) {
    322                         }
    323                     }
    324                     break;
    325                 case ActivityManager.RunningAppProcessInfo.REASON_SERVICE_IN_USE:
    326                     textid = R.string.process_service_in_use_description;
    327                     if (rpi.importanceReasonComponent != null) {
    328                         try {
    329                             ServiceInfo serv = getActivity().getPackageManager().getServiceInfo(
    330                                     rpi.importanceReasonComponent, 0);
    331                             label = RunningState.makeLabel(getActivity().getPackageManager(),
    332                                     serv.name, serv);
    333                         } catch (NameNotFoundException e) {
    334                         }
    335                     }
    336                     break;
    337             }
    338             if (textid != 0 && label != null) {
    339                 description.setText(getActivity().getString(textid, label));
    340             }
    341         }
    342 
    343         mActiveDetails.add(detail);
    344     }
    345 
    346     void addDetailViews() {
    347         for (int i=mActiveDetails.size()-1; i>=0; i--) {
    348             mAllDetails.removeView(mActiveDetails.get(i).mRootView);
    349         }
    350         mActiveDetails.clear();
    351 
    352         if (mServicesHeader != null) {
    353             mAllDetails.removeView(mServicesHeader);
    354             mServicesHeader = null;
    355         }
    356 
    357         if (mProcessesHeader != null) {
    358             mAllDetails.removeView(mProcessesHeader);
    359             mProcessesHeader = null;
    360         }
    361 
    362         mNumServices = mNumProcesses = 0;
    363 
    364         if (mMergedItem != null) {
    365             for (int i=0; i<mMergedItem.mServices.size(); i++) {
    366                 addServiceDetailsView(mMergedItem.mServices.get(i), mMergedItem);
    367             }
    368 
    369             if (mMergedItem.mServices.size() <= 0) {
    370                 // This item does not have any services, so it must be
    371                 // another interesting process...  we will put a fake service
    372                 // entry for it, to allow the user to "stop" it.
    373                 addServiceDetailsView(null, mMergedItem);
    374             }
    375 
    376             for (int i=-1; i<mMergedItem.mOtherProcesses.size(); i++) {
    377                 RunningState.ProcessItem pi = i < 0 ? mMergedItem.mProcess
    378                         : mMergedItem.mOtherProcesses.get(i);
    379                 if (pi.mPid <= 0) {
    380                     continue;
    381                 }
    382 
    383                 addProcessDetailsView(pi, i < 0);
    384             }
    385         }
    386     }
    387 
    388     void refreshUi(boolean dataChanged) {
    389         if (findMergedItem()) {
    390             dataChanged = true;
    391         }
    392         if (dataChanged) {
    393             if (mMergedItem != null) {
    394                 mSnippetActiveItem = mSnippetViewHolder.bind(mState,
    395                         mMergedItem, mBuilder);
    396             } else if (mSnippetActiveItem != null) {
    397                 // Clear whatever is currently being shown.
    398                 mSnippetActiveItem.mHolder.size.setText("");
    399                 mSnippetActiveItem.mHolder.uptime.setText("");
    400                 mSnippetActiveItem.mHolder.description.setText(R.string.no_services);
    401             } else {
    402                 // No merged item, never had one.  Nothing to do.
    403                 finish();
    404                 return;
    405             }
    406             addDetailViews();
    407         }
    408     }
    409 
    410     private void finish() {
    411         (new Handler()).post(new Runnable() {
    412             @Override
    413             public void run() {
    414                 Activity a = getActivity();
    415                 if (a != null) {
    416                     a.onBackPressed();
    417                 }
    418             }
    419         });
    420     }
    421 
    422     @Override
    423     public void onCreate(Bundle savedInstanceState) {
    424         super.onCreate(savedInstanceState);
    425 
    426         mUid = getArguments().getInt(KEY_UID, 0);
    427         mProcessName = getArguments().getString(KEY_PROCESS);
    428         mShowBackground = getArguments().getBoolean(KEY_BACKGROUND, false);
    429 
    430         mAm = (ActivityManager)getActivity().getSystemService(Context.ACTIVITY_SERVICE);
    431         mInflater = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    432 
    433         mState = RunningState.getInstance(getActivity());
    434     }
    435 
    436     @Override
    437     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    438         View view = mRootView = inflater.inflate(R.layout.running_service_details, null);
    439 
    440         mAllDetails = (ViewGroup)view.findViewById(R.id.all_details);
    441         mSnippet = (ViewGroup)view.findViewById(R.id.snippet);
    442         mSnippet.setBackgroundResource(com.android.internal.R.drawable.title_bar_medium);
    443         mSnippet.setPadding(0, mSnippet.getPaddingTop(), 0, mSnippet.getPaddingBottom());
    444         mSnippetViewHolder = new RunningProcessesView.ViewHolder(mSnippet);
    445 
    446         // We want to retrieve the data right now, so any active managed
    447         // dialog that gets created can find it.
    448         ensureData();
    449 
    450         return view;
    451     }
    452 
    453     @Override
    454     public void onPause() {
    455         super.onPause();
    456         mHaveData = false;
    457         mState.pause();
    458     }
    459 
    460     @Override
    461     public void onResume() {
    462         super.onResume();
    463         ensureData();
    464     }
    465 
    466     ActiveDetail activeDetailForService(ComponentName comp) {
    467         for (int i=0; i<mActiveDetails.size(); i++) {
    468             ActiveDetail ad = mActiveDetails.get(i);
    469             if (ad.mServiceItem != null && ad.mServiceItem.mRunningService != null
    470                     && comp.equals(ad.mServiceItem.mRunningService.service)) {
    471                 return ad;
    472             }
    473         }
    474         return null;
    475     }
    476 
    477     private void showConfirmStopDialog(ComponentName comp) {
    478         DialogFragment newFragment = MyAlertDialogFragment.newConfirmStop(
    479                 DIALOG_CONFIRM_STOP, comp);
    480         newFragment.setTargetFragment(this, 0);
    481         newFragment.show(getFragmentManager(), "confirmstop");
    482     }
    483 
    484     public static class MyAlertDialogFragment extends DialogFragment {
    485 
    486         public static MyAlertDialogFragment newConfirmStop(int id, ComponentName comp) {
    487             MyAlertDialogFragment frag = new MyAlertDialogFragment();
    488             Bundle args = new Bundle();
    489             args.putInt("id", id);
    490             args.putParcelable("comp", comp);
    491             frag.setArguments(args);
    492             return frag;
    493         }
    494 
    495         RunningServiceDetails getOwner() {
    496             return (RunningServiceDetails)getTargetFragment();
    497         }
    498 
    499         @Override
    500         public Dialog onCreateDialog(Bundle savedInstanceState) {
    501             int id = getArguments().getInt("id");
    502             switch (id) {
    503                 case DIALOG_CONFIRM_STOP: {
    504                     final ComponentName comp = (ComponentName)getArguments().getParcelable("comp");
    505                     if (getOwner().activeDetailForService(comp) == null) {
    506                         return null;
    507                     }
    508 
    509                     return new AlertDialog.Builder(getActivity())
    510                             .setTitle(getActivity().getString(R.string.runningservicedetails_stop_dlg_title))
    511                             .setIcon(android.R.drawable.ic_dialog_alert)
    512                             .setMessage(getActivity().getString(R.string.runningservicedetails_stop_dlg_text))
    513                             .setPositiveButton(R.string.dlg_ok,
    514                                     new DialogInterface.OnClickListener() {
    515                                 public void onClick(DialogInterface dialog, int which) {
    516                                     ActiveDetail ad = getOwner().activeDetailForService(comp);
    517                                     if (ad != null) {
    518                                         ad.stopActiveService(true);
    519                                     }
    520                                 }
    521                             })
    522                             .setNegativeButton(R.string.dlg_cancel, null)
    523                             .create();
    524                 }
    525             }
    526             throw new IllegalArgumentException("unknown id " + id);
    527         }
    528     }
    529 
    530     void ensureData() {
    531         if (!mHaveData) {
    532             mHaveData = true;
    533             mState.resume(this);
    534 
    535             // We want to go away if the service being shown no longer exists,
    536             // so we need to ensure we have done the initial data retrieval before
    537             // showing our ui.
    538             mState.waitForData();
    539 
    540             // And since we know we have the data, let's show the UI right away
    541             // to avoid flicker.
    542             refreshUi(true);
    543         }
    544     }
    545 
    546     void updateTimes() {
    547         if (mSnippetActiveItem != null) {
    548             mSnippetActiveItem.updateTime(getActivity(), mBuilder);
    549         }
    550         for (int i=0; i<mActiveDetails.size(); i++) {
    551             mActiveDetails.get(i).mActiveItem.updateTime(getActivity(), mBuilder);
    552         }
    553     }
    554 
    555     @Override
    556     public void onRefreshUi(int what) {
    557         switch (what) {
    558             case REFRESH_TIME:
    559                 updateTimes();
    560                 break;
    561             case REFRESH_DATA:
    562                 refreshUi(false);
    563                 updateTimes();
    564                 break;
    565             case REFRESH_STRUCTURE:
    566                 refreshUi(true);
    567                 updateTimes();
    568                 break;
    569         }
    570     }
    571 }
    572