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.content.pm.ApplicationInfo;
20import android.content.pm.PackageInfo;
21import android.content.pm.PackageManager;
22import android.os.Parcel;
23import android.os.Parcelable;
24import android.util.ArrayMap;
25import android.util.Log;
26import android.util.SparseArray;
27import com.android.internal.app.ProcessStats;
28
29import java.util.ArrayList;
30import java.util.Collections;
31import java.util.Comparator;
32
33public final class ProcStatsEntry implements Parcelable {
34    private static final String TAG = "ProcStatsEntry";
35    private static boolean DEBUG = ProcessStatsUi.DEBUG;
36
37    final String mPackage;
38    final int mUid;
39    final String mName;
40    final ArrayList<String> mPackages = new ArrayList<String>();
41    final long mDuration;
42    final long mAvgPss;
43    final long mMaxPss;
44    final long mAvgUss;
45    final long mMaxUss;
46    final long mWeight;
47
48    String mBestTargetPackage;
49
50    ArrayMap<String, ArrayList<Service>> mServices = new ArrayMap<String, ArrayList<Service>>(1);
51
52    public ApplicationInfo mUiTargetApp;
53    public String mUiLabel;
54    public String mUiBaseLabel;
55    public String mUiPackage;
56
57    public ProcStatsEntry(ProcessStats.ProcessState proc, String packageName,
58            ProcessStats.ProcessDataCollection tmpTotals, boolean useUss, boolean weightWithTime) {
59        ProcessStats.computeProcessData(proc, tmpTotals, 0);
60        mPackage = proc.mPackage;
61        mUid = proc.mUid;
62        mName = proc.mName;
63        mPackages.add(packageName);
64        mDuration = tmpTotals.totalTime;
65        mAvgPss = tmpTotals.avgPss;
66        mMaxPss = tmpTotals.maxPss;
67        mAvgUss = tmpTotals.avgUss;
68        mMaxUss = tmpTotals.maxUss;
69        mWeight = (weightWithTime ? mDuration : 1) * (useUss ? mAvgUss : mAvgPss);
70        if (DEBUG) Log.d(TAG, "New proc entry " + proc.mName + ": dur=" + mDuration
71                + " avgpss=" + mAvgPss + " weight=" + mWeight);
72    }
73
74    public ProcStatsEntry(Parcel in) {
75        mPackage = in.readString();
76        mUid = in.readInt();
77        mName = in.readString();
78        in.readStringList(mPackages);
79        mDuration = in.readLong();
80        mAvgPss = in.readLong();
81        mMaxPss = in.readLong();
82        mAvgUss = in.readLong();
83        mMaxUss = in.readLong();
84        mWeight = in.readLong();
85        mBestTargetPackage = in.readString();
86        final int N = in.readInt();
87        if (N > 0) {
88            mServices.ensureCapacity(N);
89            for (int i=0; i<N; i++) {
90                String key = in.readString();
91                ArrayList<Service> value = new ArrayList<Service>();
92                in.readTypedList(value, Service.CREATOR);
93                mServices.append(key, value);
94            }
95        }
96    }
97
98    public void addPackage(String packageName) {
99        mPackages.add(packageName);
100    }
101
102    public void evaluateTargetPackage(PackageManager pm, ProcessStats stats,
103            ProcessStats.ProcessDataCollection totals, Comparator<ProcStatsEntry> compare,
104            boolean useUss, boolean weightWithTime) {
105        mBestTargetPackage = null;
106        if (mPackages.size() == 1) {
107            if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": single pkg " + mPackages.get(0));
108            mBestTargetPackage = mPackages.get(0);
109        } else {
110            // See if there is one significant package that was running here.
111            ArrayList<ProcStatsEntry> subProcs = new ArrayList<ProcStatsEntry>();
112            for (int ipkg=0; ipkg<mPackages.size(); ipkg++) {
113                SparseArray<ProcessStats.PackageState> vpkgs
114                        = stats.mPackages.get(mPackages.get(ipkg), mUid);
115                for (int ivers=0;  ivers<vpkgs.size(); ivers++) {
116                    ProcessStats.PackageState pkgState = vpkgs.valueAt(ivers);
117                    if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ", pkg "
118                            + pkgState + ":");
119                    if (pkgState == null) {
120                        Log.w(TAG, "No package state found for " + mPackages.get(ipkg) + "/"
121                                + mUid + " in process " + mName);
122                        continue;
123                    }
124                    ProcessStats.ProcessState pkgProc = pkgState.mProcesses.get(mName);
125                    if (pkgProc == null) {
126                        Log.w(TAG, "No process " + mName + " found in package state "
127                                + mPackages.get(ipkg) + "/" + mUid);
128                        continue;
129                    }
130                    subProcs.add(new ProcStatsEntry(pkgProc, pkgState.mPackageName, totals, useUss,
131                            weightWithTime));
132                }
133            }
134            if (subProcs.size() > 1) {
135                Collections.sort(subProcs, compare);
136                if (subProcs.get(0).mWeight > (subProcs.get(1).mWeight*3)) {
137                    if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": best pkg "
138                            + subProcs.get(0).mPackage + " weight " + subProcs.get(0).mWeight
139                            + " better than " + subProcs.get(1).mPackage
140                            + " weight " + subProcs.get(1).mWeight);
141                    mBestTargetPackage = subProcs.get(0).mPackage;
142                    return;
143                }
144                // Couldn't find one that is best by weight, let's decide on best another
145                // way: the one that has the longest running service, accounts for at least
146                // half of the maximum weight, and has specified an explicit app icon.
147                long maxWeight = subProcs.get(0).mWeight;
148                long bestRunTime = -1;
149                for (int i=0; i<subProcs.size(); i++) {
150                    if (subProcs.get(i).mWeight < (maxWeight/2)) {
151                        if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": pkg "
152                                + subProcs.get(i).mPackage + " weight " + subProcs.get(i).mWeight
153                                + " too small");
154                        continue;
155                    }
156                    try {
157                        ApplicationInfo ai = pm.getApplicationInfo(subProcs.get(i).mPackage, 0);
158                        if (ai.icon == 0) {
159                            if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": pkg "
160                                    + subProcs.get(i).mPackage + " has no icon");
161                            continue;
162                        }
163                    } catch (PackageManager.NameNotFoundException e) {
164                        if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": pkg "
165                                + subProcs.get(i).mPackage + " failed finding app info");
166                        continue;
167                    }
168                    ArrayList<Service> subProcServices = null;
169                    for (int isp=0, NSP=mServices.size(); isp<NSP; isp++) {
170                        ArrayList<Service> subServices = mServices.valueAt(isp);
171                        if (subServices.get(0).mPackage.equals(subProcs.get(i).mPackage)) {
172                            subProcServices = subServices;
173                            break;
174                        }
175                    }
176                    long thisRunTime = 0;
177                    if (subProcServices != null) {
178                        for (int iss=0, NSS=subProcServices.size(); iss<NSS; iss++) {
179                            Service service = subProcServices.get(iss);
180                            if (service.mDuration > thisRunTime) {
181                                if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": pkg "
182                                        + subProcs.get(i).mPackage + " service " + service.mName
183                                        + " run time is " + service.mDuration);
184                                thisRunTime = service.mDuration;
185                                break;
186                            }
187                        }
188                    }
189                    if (thisRunTime > bestRunTime) {
190                        if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": pkg "
191                                + subProcs.get(i).mPackage + " new best run time " + thisRunTime);
192                        mBestTargetPackage = subProcs.get(i).mPackage;
193                        bestRunTime = thisRunTime;
194                    } else {
195                        if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": pkg "
196                                + subProcs.get(i).mPackage + " run time " + thisRunTime
197                                + " not as good as last " + bestRunTime);
198                    }
199                }
200            } else if (subProcs.size() == 1) {
201                mBestTargetPackage = subProcs.get(0).mPackage;
202            }
203        }
204    }
205
206    public void retrieveUiData(PackageManager pm) {
207        mUiTargetApp = null;
208        mUiLabel = mUiBaseLabel = mName;
209        mUiPackage = mBestTargetPackage;
210        if (mUiPackage != null) {
211            // Only one app associated with this process.
212            try {
213                mUiTargetApp = pm.getApplicationInfo(mUiPackage,
214                        PackageManager.GET_DISABLED_COMPONENTS |
215                        PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS |
216                        PackageManager.GET_UNINSTALLED_PACKAGES);
217                String name = mUiBaseLabel = mUiTargetApp.loadLabel(pm).toString();
218                if (mName.equals(mUiPackage)) {
219                    mUiLabel = name;
220                } else {
221                    if (mName.startsWith(mUiPackage)) {
222                        int off = mUiPackage.length();
223                        if (mName.length() > off) {
224                            off++;
225                        }
226                        mUiLabel = name + " (" + mName.substring(off) + ")";
227                    } else {
228                        mUiLabel = name + " (" + mName + ")";
229                    }
230                }
231            } catch (PackageManager.NameNotFoundException e) {
232            }
233        }
234        if (mUiTargetApp == null) {
235            String[] packages = pm.getPackagesForUid(mUid);
236            if (packages != null) {
237                for (String curPkg : packages) {
238                    try {
239                        final PackageInfo pi = pm.getPackageInfo(curPkg,
240                                PackageManager.GET_DISABLED_COMPONENTS |
241                                PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS |
242                                PackageManager.GET_UNINSTALLED_PACKAGES);
243                        if (pi.sharedUserLabel != 0) {
244                            mUiTargetApp = pi.applicationInfo;
245                            final CharSequence nm = pm.getText(curPkg,
246                                    pi.sharedUserLabel, pi.applicationInfo);
247                            if (nm != null) {
248                                mUiBaseLabel = nm.toString();
249                                mUiLabel = mUiBaseLabel + " (" + mName + ")";
250                            } else {
251                                mUiBaseLabel = mUiTargetApp.loadLabel(pm).toString();
252                                mUiLabel = mUiBaseLabel + " (" + mName + ")";
253                            }
254                            break;
255                        }
256                    } catch (PackageManager.NameNotFoundException e) {
257                    }
258                }
259            } else {
260                // no current packages for this uid, typically because of uninstall
261                Log.i(TAG, "No package for uid " + mUid);
262            }
263        }
264    }
265
266    public void addService(ProcessStats.ServiceState svc) {
267        ArrayList<Service> services = mServices.get(svc.mPackage);
268        if (services == null) {
269            services = new ArrayList<Service>();
270            mServices.put(svc.mPackage, services);
271        }
272        services.add(new Service(svc));
273    }
274
275    @Override
276    public int describeContents() {
277        return 0;
278    }
279
280    @Override
281    public void writeToParcel(Parcel dest, int flags) {
282        dest.writeString(mPackage);
283        dest.writeInt(mUid);
284        dest.writeString(mName);
285        dest.writeStringList(mPackages);
286        dest.writeLong(mDuration);
287        dest.writeLong(mAvgPss);
288        dest.writeLong(mMaxPss);
289        dest.writeLong(mAvgUss);
290        dest.writeLong(mMaxUss);
291        dest.writeLong(mWeight);
292        dest.writeString(mBestTargetPackage);
293        final int N = mServices.size();
294        dest.writeInt(N);
295        for (int i=0; i<N; i++) {
296            dest.writeString(mServices.keyAt(i));
297            dest.writeTypedList(mServices.valueAt(i));
298        }
299    }
300
301    public static final Parcelable.Creator<ProcStatsEntry> CREATOR
302            = new Parcelable.Creator<ProcStatsEntry>() {
303        public ProcStatsEntry createFromParcel(Parcel in) {
304            return new ProcStatsEntry(in);
305        }
306
307        public ProcStatsEntry[] newArray(int size) {
308            return new ProcStatsEntry[size];
309        }
310    };
311
312    public static final class Service implements Parcelable {
313        final String mPackage;
314        final String mName;
315        final String mProcess;
316        final long mDuration;
317
318        public Service(ProcessStats.ServiceState service) {
319            mPackage = service.mPackage;
320            mName = service.mName;
321            mProcess = service.mProcessName;
322            mDuration = ProcessStats.dumpSingleServiceTime(null, null, service,
323                    ProcessStats.ServiceState.SERVICE_RUN,
324                    ProcessStats.STATE_NOTHING, 0, 0);
325        }
326
327        public Service(Parcel in) {
328            mPackage = in.readString();
329            mName = in.readString();
330            mProcess = in.readString();
331            mDuration = in.readLong();
332        }
333
334        @Override
335        public int describeContents() {
336            return 0;
337        }
338
339        @Override
340        public void writeToParcel(Parcel dest, int flags) {
341            dest.writeString(mPackage);
342            dest.writeString(mName);
343            dest.writeString(mProcess);
344            dest.writeLong(mDuration);
345        }
346
347        public static final Parcelable.Creator<Service> CREATOR
348                = new Parcelable.Creator<Service>() {
349            public Service createFromParcel(Parcel in) {
350                return new Service(in);
351            }
352
353            public Service[] newArray(int size) {
354                return new Service[size];
355            }
356        };
357    }
358}
359