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