1/*
2 * Copyright (C) 2013 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
17package com.android.settings.applications;
18
19import android.app.Activity;
20import android.app.ActivityManager;
21import android.app.Fragment;
22import android.app.admin.DevicePolicyManager;
23import android.content.BroadcastReceiver;
24import android.content.Context;
25import android.content.Intent;
26import android.content.pm.ApplicationInfo;
27import android.content.pm.PackageManager;
28import android.content.res.Resources;
29import android.net.Uri;
30import android.os.Bundle;
31import android.os.Process;
32import android.os.UserHandle;
33import android.text.format.Formatter;
34import android.view.LayoutInflater;
35import android.view.View;
36import android.view.ViewGroup;
37import android.widget.Button;
38import android.widget.ImageView;
39import android.widget.ProgressBar;
40import android.widget.TextView;
41import com.android.settings.R;
42import com.android.settings.Utils;
43
44import java.util.ArrayList;
45import java.util.Collections;
46import java.util.Comparator;
47
48import static com.android.settings.Utils.prepareCustomPreferencesList;
49
50public class ProcessStatsDetail extends Fragment implements Button.OnClickListener {
51    private static final String TAG = "ProcessStatsDetail";
52
53    public static final int ACTION_FORCE_STOP = 1;
54
55    public static final String EXTRA_ENTRY = "entry";
56    public static final String EXTRA_USE_USS = "use_uss";
57    public static final String EXTRA_MAX_WEIGHT = "max_weight";
58    public static final String EXTRA_TOTAL_TIME = "total_time";
59
60    private PackageManager mPm;
61    private DevicePolicyManager mDpm;
62
63    private ProcStatsEntry mEntry;
64    private boolean mUseUss;
65    private long mMaxWeight;
66    private long mTotalTime;
67
68    private View mRootView;
69    private TextView mTitleView;
70    private ViewGroup mTwoButtonsPanel;
71    private Button mForceStopButton;
72    private Button mReportButton;
73    private ViewGroup mDetailsParent;
74    private ViewGroup mServicesParent;
75
76    @Override
77    public void onCreate(Bundle icicle) {
78        super.onCreate(icicle);
79        mPm = getActivity().getPackageManager();
80        mDpm = (DevicePolicyManager)getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
81        final Bundle args = getArguments();
82        mEntry = (ProcStatsEntry)args.getParcelable(EXTRA_ENTRY);
83        mEntry.retrieveUiData(mPm);
84        mUseUss = args.getBoolean(EXTRA_USE_USS);
85        mMaxWeight = args.getLong(EXTRA_MAX_WEIGHT);
86        mTotalTime = args.getLong(EXTRA_TOTAL_TIME);
87    }
88
89    @Override
90    public View onCreateView(
91            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
92        final View view = inflater.inflate(R.layout.process_stats_details, container, false);
93        prepareCustomPreferencesList(container, view, view, false);
94
95        mRootView = view;
96        createDetails();
97        return view;
98    }
99
100    @Override
101    public void onResume() {
102        super.onResume();
103        checkForceStop();
104    }
105
106    @Override
107    public void onPause() {
108        super.onPause();
109    }
110
111    private void createDetails() {
112        final double percentOfWeight = (((double)mEntry.mWeight) / mMaxWeight) * 100;
113
114        int appLevel = (int) Math.ceil(percentOfWeight);
115        String appLevelText = Utils.formatPercentage(mEntry.mDuration, mTotalTime);
116
117        // Set all values in the header.
118        final TextView summary = (TextView) mRootView.findViewById(android.R.id.summary);
119        summary.setText(mEntry.mName);
120        summary.setVisibility(View.VISIBLE);
121        mTitleView = (TextView) mRootView.findViewById(android.R.id.title);
122        mTitleView.setText(mEntry.mUiBaseLabel);
123        final TextView text1 = (TextView)mRootView.findViewById(android.R.id.text1);
124        text1.setText(appLevelText);
125        final ProgressBar progress = (ProgressBar) mRootView.findViewById(android.R.id.progress);
126        progress.setProgress(appLevel);
127        final ImageView icon = (ImageView) mRootView.findViewById(android.R.id.icon);
128        if (mEntry.mUiTargetApp != null) {
129            icon.setImageDrawable(mEntry.mUiTargetApp.loadIcon(mPm));
130        }
131
132        mTwoButtonsPanel = (ViewGroup)mRootView.findViewById(R.id.two_buttons_panel);
133        mForceStopButton = (Button)mRootView.findViewById(R.id.right_button);
134        mReportButton = (Button)mRootView.findViewById(R.id.left_button);
135        mForceStopButton.setEnabled(false);
136        mReportButton.setVisibility(View.INVISIBLE);
137
138        mDetailsParent = (ViewGroup)mRootView.findViewById(R.id.details);
139        mServicesParent = (ViewGroup)mRootView.findViewById(R.id.services);
140
141        fillDetailsSection();
142        fillServicesSection();
143
144        if (mEntry.mUid >= android.os.Process.FIRST_APPLICATION_UID) {
145            mForceStopButton.setText(R.string.force_stop);
146            mForceStopButton.setTag(ACTION_FORCE_STOP);
147            mForceStopButton.setOnClickListener(this);
148            mTwoButtonsPanel.setVisibility(View.VISIBLE);
149        } else {
150            mTwoButtonsPanel.setVisibility(View.GONE);
151        }
152    }
153
154    public void onClick(View v) {
155        doAction((Integer) v.getTag());
156    }
157
158    private void doAction(int action) {
159        switch (action) {
160            case ACTION_FORCE_STOP:
161                killProcesses();
162                break;
163        }
164    }
165
166    private void addPackageHeaderItem(ViewGroup parent, String packageName) {
167        LayoutInflater inflater = getActivity().getLayoutInflater();
168        ViewGroup item = (ViewGroup) inflater.inflate(R.layout.running_processes_item,
169                null);
170        parent.addView(item);
171        final ImageView icon = (ImageView) item.findViewById(R.id.icon);
172        TextView nameView = (TextView) item.findViewById(R.id.name);
173        TextView descriptionView = (TextView) item.findViewById(R.id.description);
174        try {
175            ApplicationInfo ai = mPm.getApplicationInfo(packageName, 0);
176            icon.setImageDrawable(ai.loadIcon(mPm));
177            nameView.setText(ai.loadLabel(mPm));
178        } catch (PackageManager.NameNotFoundException e) {
179        }
180        descriptionView.setText(packageName);
181    }
182
183    private void addDetailsItem(ViewGroup parent, CharSequence label, CharSequence value) {
184        LayoutInflater inflater = getActivity().getLayoutInflater();
185        ViewGroup item = (ViewGroup) inflater.inflate(R.layout.power_usage_detail_item_text,
186                null);
187        parent.addView(item);
188        TextView labelView = (TextView) item.findViewById(R.id.label);
189        TextView valueView = (TextView) item.findViewById(R.id.value);
190        labelView.setText(label);
191        valueView.setText(value);
192    }
193
194    private void fillDetailsSection() {
195        addDetailsItem(mDetailsParent, getResources().getText(R.string.process_stats_avg_ram_use),
196                Formatter.formatShortFileSize(getActivity(),
197                        (mUseUss ? mEntry.mAvgUss : mEntry.mAvgPss) * 1024));
198        addDetailsItem(mDetailsParent, getResources().getText(R.string.process_stats_max_ram_use),
199                Formatter.formatShortFileSize(getActivity(),
200                        (mUseUss ? mEntry.mMaxUss : mEntry.mMaxPss) * 1024));
201        addDetailsItem(mDetailsParent, getResources().getText(R.string.process_stats_run_time),
202                Utils.formatPercentage(mEntry.mDuration, mTotalTime));
203    }
204
205    final static Comparator<ProcStatsEntry.Service> sServiceCompare
206            = new Comparator<ProcStatsEntry.Service>() {
207        @Override
208        public int compare(ProcStatsEntry.Service lhs, ProcStatsEntry.Service rhs) {
209            if (lhs.mDuration < rhs.mDuration) {
210                return 1;
211            } else if (lhs.mDuration > rhs.mDuration) {
212                return -1;
213            }
214            return 0;
215        }
216    };
217
218    final static Comparator<ArrayList<ProcStatsEntry.Service>> sServicePkgCompare
219            = new Comparator<ArrayList<ProcStatsEntry.Service>>() {
220        @Override
221        public int compare(ArrayList<ProcStatsEntry.Service> lhs,
222                ArrayList<ProcStatsEntry.Service> rhs) {
223            long topLhs = lhs.size() > 0 ? lhs.get(0).mDuration : 0;
224            long topRhs = rhs.size() > 0 ? rhs.get(0).mDuration : 0;
225            if (topLhs < topRhs) {
226                return 1;
227            } else if (topLhs > topRhs) {
228                return -1;
229            }
230            return 0;
231        }
232    };
233
234    private void fillServicesSection() {
235        if (mEntry.mServices.size() > 0) {
236            boolean addPackageSections = false;
237            // Sort it all.
238            ArrayList<ArrayList<ProcStatsEntry.Service>> servicePkgs
239                    = new ArrayList<ArrayList<ProcStatsEntry.Service>>();
240            for (int ip=0; ip<mEntry.mServices.size(); ip++) {
241                ArrayList<ProcStatsEntry.Service> services =
242                        (ArrayList<ProcStatsEntry.Service>)mEntry.mServices.valueAt(ip).clone();
243                Collections.sort(services, sServiceCompare);
244                servicePkgs.add(services);
245            }
246            if (mEntry.mServices.size() > 1
247                    || !mEntry.mServices.valueAt(0).get(0).mPackage.equals(mEntry.mPackage)) {
248                addPackageSections = true;
249                // Sort these so that the one(s) with the longest run durations are on top.
250                Collections.sort(servicePkgs, sServicePkgCompare);
251            }
252            for (int ip=0; ip<servicePkgs.size(); ip++) {
253                ArrayList<ProcStatsEntry.Service> services = servicePkgs.get(ip);
254                if (addPackageSections) {
255                    addPackageHeaderItem(mServicesParent, services.get(0).mPackage);
256                }
257                for (int is=0; is<services.size(); is++) {
258                    ProcStatsEntry.Service service = services.get(is);
259                    String label = service.mName;
260                    int tail = label.lastIndexOf('.');
261                    if (tail >= 0 && tail < (label.length()-1)) {
262                        label = label.substring(tail+1);
263                    }
264                    String percentage = Utils.formatPercentage(service.mDuration, mTotalTime);
265                    addDetailsItem(mServicesParent, label, percentage);
266                }
267            }
268        }
269    }
270
271    private void killProcesses() {
272        ActivityManager am = (ActivityManager)getActivity().getSystemService(
273                Context.ACTIVITY_SERVICE);
274        am.forceStopPackage(mEntry.mUiPackage);
275        checkForceStop();
276    }
277
278    private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() {
279        @Override
280        public void onReceive(Context context, Intent intent) {
281            mForceStopButton.setEnabled(getResultCode() != Activity.RESULT_CANCELED);
282        }
283    };
284
285    private void checkForceStop() {
286        if (mEntry.mUiPackage == null || mEntry.mUid < Process.FIRST_APPLICATION_UID) {
287            mForceStopButton.setEnabled(false);
288            return;
289        }
290        if (mDpm.packageHasActiveAdmins(mEntry.mUiPackage)) {
291            mForceStopButton.setEnabled(false);
292            return;
293        }
294        try {
295            ApplicationInfo info = mPm.getApplicationInfo(mEntry.mUiPackage, 0);
296            if ((info.flags&ApplicationInfo.FLAG_STOPPED) == 0) {
297                mForceStopButton.setEnabled(true);
298            }
299        } catch (PackageManager.NameNotFoundException e) {
300        }
301        Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART,
302                Uri.fromParts("package", mEntry.mUiPackage, null));
303        intent.putExtra(Intent.EXTRA_PACKAGES, new String[] { mEntry.mUiPackage });
304        intent.putExtra(Intent.EXTRA_UID, mEntry.mUid);
305        intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(mEntry.mUid));
306        getActivity().sendOrderedBroadcast(intent, null, mCheckKillProcessesReceiver, null,
307                Activity.RESULT_CANCELED, null, null);
308    }
309}
310