blob: eafd22b1ec8f0a3d2520a6ddf2af141f25bcba18 [file] [log] [blame]
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -07001/*
2 * Copyright (C) 2011 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;
18
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -070019import static android.net.NetworkPolicy.LIMIT_DISABLED;
Jeff Sharkeydd6efe12011-06-15 10:31:41 -070020import static android.net.NetworkPolicyManager.ACTION_DATA_USAGE_LIMIT;
21import static android.net.NetworkPolicyManager.ACTION_DATA_USAGE_WARNING;
22import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
Jeff Sharkey8a503642011-06-10 13:31:21 -070023import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
24import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
25import static android.net.TrafficStats.TEMPLATE_MOBILE_3G_LOWER;
26import static android.net.TrafficStats.TEMPLATE_MOBILE_4G;
27import static android.net.TrafficStats.TEMPLATE_MOBILE_ALL;
28import static android.net.TrafficStats.TEMPLATE_WIFI;
29import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070030
Jeff Sharkey4c72ae52011-06-14 15:01:18 -070031import android.app.AlertDialog;
32import android.app.Dialog;
33import android.app.DialogFragment;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070034import android.app.Fragment;
35import android.content.Context;
Jeff Sharkey4c72ae52011-06-14 15:01:18 -070036import android.content.DialogInterface;
Jeff Sharkey4dfa6602011-06-13 00:42:03 -070037import android.content.Intent;
Jeff Sharkey8e911d72011-06-14 22:41:21 -070038import android.content.pm.ApplicationInfo;
39import android.content.pm.PackageInfo;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070040import android.content.pm.PackageManager;
Jeff Sharkey8e911d72011-06-14 22:41:21 -070041import android.content.pm.PackageManager.NameNotFoundException;
Jeff Sharkey8a503642011-06-10 13:31:21 -070042import android.net.INetworkPolicyManager;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070043import android.net.INetworkStatsService;
Jeff Sharkey8a503642011-06-10 13:31:21 -070044import android.net.NetworkPolicy;
Jeff Sharkeydd6efe12011-06-15 10:31:41 -070045import android.net.NetworkPolicyManager;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070046import android.net.NetworkStats;
47import android.net.NetworkStatsHistory;
Jeff Sharkeyaa5260e2011-06-14 23:21:59 -070048import android.os.AsyncTask;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070049import android.os.Bundle;
50import android.os.RemoteException;
51import android.os.ServiceManager;
Jeff Sharkey8a503642011-06-10 13:31:21 -070052import android.preference.CheckBoxPreference;
53import android.preference.Preference;
Jeff Sharkey4dfa6602011-06-13 00:42:03 -070054import android.preference.PreferenceActivity;
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -070055import android.preference.SwitchPreference;
56import android.telephony.TelephonyManager;
Jeff Sharkey8e911d72011-06-14 22:41:21 -070057import android.text.TextUtils;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070058import android.text.format.DateUtils;
59import android.text.format.Formatter;
Jeff Sharkey8a503642011-06-10 13:31:21 -070060import android.text.format.Time;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070061import android.util.Log;
62import android.view.LayoutInflater;
Jeff Sharkey8a503642011-06-10 13:31:21 -070063import android.view.Menu;
64import android.view.MenuInflater;
65import android.view.MenuItem;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070066import android.view.View;
Jeff Sharkey8a503642011-06-10 13:31:21 -070067import android.view.View.OnClickListener;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070068import android.view.ViewGroup;
Jeff Sharkey8a503642011-06-10 13:31:21 -070069import android.widget.AbsListView;
70import android.widget.AdapterView;
71import android.widget.AdapterView.OnItemClickListener;
72import android.widget.AdapterView.OnItemSelectedListener;
73import android.widget.ArrayAdapter;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070074import android.widget.BaseAdapter;
Jeff Sharkey8a503642011-06-10 13:31:21 -070075import android.widget.LinearLayout;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070076import android.widget.ListView;
Jeff Sharkey4c72ae52011-06-14 15:01:18 -070077import android.widget.NumberPicker;
Jeff Sharkey8a503642011-06-10 13:31:21 -070078import android.widget.Spinner;
79import android.widget.TabHost;
80import android.widget.TabHost.OnTabChangeListener;
81import android.widget.TabHost.TabContentFactory;
82import android.widget.TabHost.TabSpec;
83import android.widget.TabWidget;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070084import android.widget.TextView;
85
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -070086import com.android.settings.net.NetworkPolicyModifier;
Jeff Sharkey8a503642011-06-10 13:31:21 -070087import com.android.settings.widget.DataUsageChartView;
88import com.android.settings.widget.DataUsageChartView.DataUsageChartListener;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070089import com.google.android.collect.Lists;
90
91import java.util.ArrayList;
Jeff Sharkey8a503642011-06-10 13:31:21 -070092import java.util.Arrays;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070093import java.util.Collections;
Jeff Sharkey8a503642011-06-10 13:31:21 -070094import java.util.Locale;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070095
96public class DataUsageSummary extends Fragment {
97 private static final String TAG = "DataUsage";
Jeff Sharkey8a503642011-06-10 13:31:21 -070098 private static final boolean LOGD = true;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070099
Jeff Sharkey8a503642011-06-10 13:31:21 -0700100 private static final int TEMPLATE_INVALID = -1;
101
102 private static final String TAB_3G = "3g";
103 private static final String TAB_4G = "4g";
104 private static final String TAB_MOBILE = "mobile";
105 private static final String TAB_WIFI = "wifi";
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700106
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700107 private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
108 private static final String TAG_CYCLE_EDITOR = "cycleEditor";
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700109 private static final String TAG_POLICY_LIMIT = "policyLimit";
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700110
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700111 private static final long KB_IN_BYTES = 1024;
112 private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
113 private static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
114
115 private INetworkStatsService mStatsService;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700116 private INetworkPolicyManager mPolicyService;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700117
Jeff Sharkey8a503642011-06-10 13:31:21 -0700118 private TabHost mTabHost;
119 private TabWidget mTabWidget;
120 private ListView mListView;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700121 private DataUsageAdapter mAdapter;
122
Jeff Sharkey8a503642011-06-10 13:31:21 -0700123 private View mHeader;
124 private LinearLayout mSwitches;
125
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700126 private SwitchPreference mDataEnabled;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700127 private CheckBoxPreference mDisableAtLimit;
128 private View mDataEnabledView;
129 private View mDisableAtLimitView;
130
131 private DataUsageChartView mChart;
132
133 private Spinner mCycleSpinner;
134 private CycleAdapter mCycleAdapter;
135
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700136 // TODO: persist show wifi flag
Jeff Sharkey8a503642011-06-10 13:31:21 -0700137 private boolean mShowWifi = false;
138
139 private int mTemplate = TEMPLATE_INVALID;
140
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700141 private NetworkPolicyModifier mPolicyModifier;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700142 private NetworkStatsHistory mHistory;
143
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700144 private String mIntentTab = null;
145
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700146 @Override
Jeff Sharkey8a503642011-06-10 13:31:21 -0700147 public void onCreate(Bundle savedInstanceState) {
148 super.onCreate(savedInstanceState);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700149
150 mStatsService = INetworkStatsService.Stub.asInterface(
151 ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
Jeff Sharkey8a503642011-06-10 13:31:21 -0700152 mPolicyService = INetworkPolicyManager.Stub.asInterface(
153 ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700154
155 final Context context = getActivity();
156 final String subscriberId = getActiveSubscriberId(context);
157 mPolicyModifier = new NetworkPolicyModifier(mPolicyService, subscriberId);
Jeff Sharkey94a90952011-06-13 22:31:09 -0700158 mPolicyModifier.read();
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700159
160 setHasOptionsMenu(true);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700161 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700162
Jeff Sharkey8a503642011-06-10 13:31:21 -0700163 @Override
164 public View onCreateView(LayoutInflater inflater, ViewGroup container,
165 Bundle savedInstanceState) {
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700166
Jeff Sharkey8a503642011-06-10 13:31:21 -0700167 final Context context = inflater.getContext();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700168 final View view = inflater.inflate(R.layout.data_usage_summary, container, false);
169
Jeff Sharkey8a503642011-06-10 13:31:21 -0700170 mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
171 mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs);
172 mListView = (ListView) view.findViewById(android.R.id.list);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700173
Jeff Sharkey8a503642011-06-10 13:31:21 -0700174 mTabHost.setup();
175 mTabHost.setOnTabChangedListener(mTabListener);
176
177 mHeader = inflater.inflate(R.layout.data_usage_header, mListView, false);
178 mListView.addHeaderView(mHeader, null, false);
179
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700180 mDataEnabled = new SwitchPreference(context);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700181 mDisableAtLimit = new CheckBoxPreference(context);
182
183 // kick refresh once to force-create views
184 refreshPreferenceViews();
185
186 // TODO: remove once thin preferences are supported (48dip)
187 mDataEnabledView.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 72));
188 mDisableAtLimitView.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 72));
189
190 mDataEnabledView.setOnClickListener(mDataEnabledListener);
191 mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
192
193 mSwitches = (LinearLayout) mHeader.findViewById(R.id.switches);
194 mSwitches.addView(mDataEnabledView);
195 mSwitches.addView(mDisableAtLimitView);
196
197 mCycleSpinner = (Spinner) mHeader.findViewById(R.id.cycles);
198 mCycleAdapter = new CycleAdapter(context);
199 mCycleSpinner.setAdapter(mCycleAdapter);
200 mCycleSpinner.setOnItemSelectedListener(mCycleListener);
201
202 mChart = new DataUsageChartView(context);
203 mChart.setListener(mChartListener);
204 mChart.setLayoutParams(new AbsListView.LayoutParams(MATCH_PARENT, 350));
205 mListView.addHeaderView(mChart, null, false);
206
207 mAdapter = new DataUsageAdapter();
208 mListView.setOnItemClickListener(mListListener);
209 mListView.setAdapter(mAdapter);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700210
211 return view;
212 }
213
214 @Override
215 public void onResume() {
216 super.onResume();
217
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700218 // pick default tab based on incoming intent
219 final Intent intent = getActivity().getIntent();
220 mIntentTab = computeTabFromIntent(intent);
221
Jeff Sharkey8a503642011-06-10 13:31:21 -0700222 // this kicks off chain reaction which creates tabs, binds the body to
223 // selected network, and binds chart, cycles and detail list.
224 updateTabs();
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700225
226 // template and tab has been selected; show dialog if limit passed
227 final String action = intent.getAction();
228 if (ACTION_DATA_USAGE_LIMIT.equals(action)) {
229 PolicyLimitFragment.show(this);
230 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700231 }
232
Jeff Sharkey8a503642011-06-10 13:31:21 -0700233 @Override
234 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
235 inflater.inflate(R.menu.data_usage, menu);
236 }
237
238 @Override
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700239 public void onPrepareOptionsMenu(Menu menu) {
240 final MenuItem split4g = menu.findItem(R.id.action_split_4g);
241 split4g.setChecked(mPolicyModifier.isMobilePolicySplit());
242 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700243
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700244 @Override
245 public boolean onOptionsItemSelected(MenuItem item) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700246 switch (item.getItemId()) {
247 case R.id.action_split_4g: {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700248 final boolean mobileSplit = !item.isChecked();
249 mPolicyModifier.setMobilePolicySplit(mobileSplit);
250 item.setChecked(mPolicyModifier.isMobilePolicySplit());
Jeff Sharkey8a503642011-06-10 13:31:21 -0700251 updateTabs();
252 return true;
253 }
254 case R.id.action_show_wifi: {
255 mShowWifi = !item.isChecked();
256 item.setChecked(mShowWifi);
257 updateTabs();
258 return true;
259 }
260 }
261 return false;
262 }
263
Jeff Sharkey94a90952011-06-13 22:31:09 -0700264 @Override
265 public void onDestroyView() {
266 super.onDestroyView();
267
268 mDataEnabledView = null;
269 mDisableAtLimitView = null;
270 }
271
Jeff Sharkey8a503642011-06-10 13:31:21 -0700272 /**
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700273 * Rebuild all tabs based on {@link NetworkPolicyModifier} and
274 * {@link #mShowWifi}, hiding the tabs entirely when applicable. Selects
275 * first tab, and kicks off a full rebind of body contents.
Jeff Sharkey8a503642011-06-10 13:31:21 -0700276 */
277 private void updateTabs() {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700278 final boolean mobileSplit = mPolicyModifier.isMobilePolicySplit();
279 final boolean tabsVisible = mobileSplit || mShowWifi;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700280 mTabWidget.setVisibility(tabsVisible ? View.VISIBLE : View.GONE);
281 mTabHost.clearAllTabs();
282
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700283 if (mobileSplit) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700284 mTabHost.addTab(buildTabSpec(TAB_3G, R.string.data_usage_tab_3g));
285 mTabHost.addTab(buildTabSpec(TAB_4G, R.string.data_usage_tab_4g));
286 }
287
288 if (mShowWifi) {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700289 if (!mobileSplit) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700290 mTabHost.addTab(buildTabSpec(TAB_MOBILE, R.string.data_usage_tab_mobile));
291 }
292 mTabHost.addTab(buildTabSpec(TAB_WIFI, R.string.data_usage_tab_wifi));
293 }
294
295 if (mTabWidget.getTabCount() > 0) {
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700296 if (mIntentTab != null) {
297 // select default tab, which will kick off updateBody()
298 mTabHost.setCurrentTabByTag(mIntentTab);
299 } else {
300 // select first tab, which will kick off updateBody()
301 mTabHost.setCurrentTab(0);
302 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700303 } else {
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700304 // no tabs visible; update body manually
Jeff Sharkey8a503642011-06-10 13:31:21 -0700305 updateBody();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700306 }
307 }
308
Jeff Sharkey8a503642011-06-10 13:31:21 -0700309 /**
310 * Factory that provide empty {@link View} to make {@link TabHost} happy.
311 */
312 private TabContentFactory mEmptyTabContent = new TabContentFactory() {
313 /** {@inheritDoc} */
314 public View createTabContent(String tag) {
315 return new View(mTabHost.getContext());
316 }
317 };
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700318
Jeff Sharkey8a503642011-06-10 13:31:21 -0700319 /**
320 * Build {@link TabSpec} with thin indicator, and empty content.
321 */
322 private TabSpec buildTabSpec(String tag, int titleRes) {
323 final LayoutInflater inflater = LayoutInflater.from(mTabWidget.getContext());
324 final View indicator = inflater.inflate(
325 R.layout.tab_indicator_thin_holo, mTabWidget, false);
326 final TextView title = (TextView) indicator.findViewById(android.R.id.title);
327 title.setText(titleRes);
328 return mTabHost.newTabSpec(tag).setIndicator(indicator).setContent(mEmptyTabContent);
329 }
330
331 private OnTabChangeListener mTabListener = new OnTabChangeListener() {
332 /** {@inheritDoc} */
333 public void onTabChanged(String tabId) {
334 // user changed tab; update body
335 updateBody();
336 }
337 };
338
339 /**
340 * Update body content based on current tab. Loads
341 * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and
342 * binds them to visible controls.
343 */
344 private void updateBody() {
345 final String tabTag = mTabHost.getCurrentTabTag();
346 final String currentTab = tabTag != null ? tabTag : TAB_MOBILE;
347
348 if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
349
350 if (TAB_WIFI.equals(currentTab)) {
351 // wifi doesn't have any controls
352 mDataEnabledView.setVisibility(View.GONE);
353 mDisableAtLimitView.setVisibility(View.GONE);
354 mTemplate = TEMPLATE_WIFI;
355
356 } else {
357 // make sure we show for non-wifi
358 mDataEnabledView.setVisibility(View.VISIBLE);
359 mDisableAtLimitView.setVisibility(View.VISIBLE);
360 }
361
362 if (TAB_MOBILE.equals(currentTab)) {
363 mDataEnabled.setTitle(R.string.data_usage_enable_mobile);
364 mDisableAtLimit.setTitle(R.string.data_usage_disable_mobile_limit);
365 mTemplate = TEMPLATE_MOBILE_ALL;
366
367 } else if (TAB_3G.equals(currentTab)) {
368 mDataEnabled.setTitle(R.string.data_usage_enable_3g);
369 mDisableAtLimit.setTitle(R.string.data_usage_disable_3g_limit);
370 mTemplate = TEMPLATE_MOBILE_3G_LOWER;
371
372 } else if (TAB_4G.equals(currentTab)) {
373 mDataEnabled.setTitle(R.string.data_usage_enable_4g);
374 mDisableAtLimit.setTitle(R.string.data_usage_disable_4g_limit);
375 mTemplate = TEMPLATE_MOBILE_4G;
376
377 }
378
379 // TODO: populate checkbox based on radio preferences
380 mDataEnabled.setChecked(true);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700381
382 try {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700383 // load stats for current template
Jeff Sharkey8a503642011-06-10 13:31:21 -0700384 mHistory = mStatsService.getHistoryForNetwork(mTemplate);
385 } catch (RemoteException e) {
386 // since we can't do much without policy or history, and we don't
387 // want to leave with half-baked UI, we bail hard.
388 throw new RuntimeException("problem reading network policy or stats", e);
389 }
390
Jeff Sharkey8a503642011-06-10 13:31:21 -0700391 // bind chart to historical stats
Jeff Sharkey8a503642011-06-10 13:31:21 -0700392 mChart.bindNetworkStats(mHistory);
393
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700394 updatePolicy(true);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700395
396 // force scroll to top of body
397 mListView.smoothScrollToPosition(0);
398
399 // kick preference views so they rebind from changes above
400 refreshPreferenceViews();
401 }
402
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700403 private void setPolicyCycleDay(int cycleDay) {
404 if (LOGD) Log.d(TAG, "setPolicyCycleDay()");
405 mPolicyModifier.setPolicyCycleDay(mTemplate, cycleDay);
406 updatePolicy(true);
407 }
408
409 private void setPolicyWarningBytes(long warningBytes) {
410 if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
411 mPolicyModifier.setPolicyWarningBytes(mTemplate, warningBytes);
412 updatePolicy(false);
413 }
414
415 private void setPolicyLimitBytes(long limitBytes) {
416 if (LOGD) Log.d(TAG, "setPolicyLimitBytes()");
417 mPolicyModifier.setPolicyLimitBytes(mTemplate, limitBytes);
418 updatePolicy(false);
419 }
420
Jeff Sharkey8a503642011-06-10 13:31:21 -0700421 /**
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700422 * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for
423 * current {@link #mTemplate}.
424 */
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700425 private void updatePolicy(boolean refreshCycle) {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700426 final NetworkPolicy policy = mPolicyModifier.getPolicy(mTemplate);
427
428 // reflect policy limit in checkbox
429 mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
430 mChart.bindNetworkPolicy(policy);
431
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700432 if (refreshCycle) {
433 // generate cycle list based on policy and available history
434 updateCycleList(policy);
435 }
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700436
437 // kick preference views so they rebind from changes above
438 refreshPreferenceViews();
439 }
440
441 /**
Jeff Sharkey8a503642011-06-10 13:31:21 -0700442 * Return full time bounds (earliest and latest time recorded) of the given
443 * {@link NetworkStatsHistory}.
444 */
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700445 public static long[] getHistoryBounds(NetworkStatsHistory history) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700446 final long currentTime = System.currentTimeMillis();
447
448 long start = currentTime;
449 long end = currentTime;
450 if (history.bucketCount > 0) {
451 start = history.bucketStart[0];
452 end = history.bucketStart[history.bucketCount - 1];
453 }
454
455 return new long[] { start, end };
456 }
457
458 /**
459 * Rebuild {@link #mCycleAdapter} based on {@link NetworkPolicy#cycleDay}
460 * and available {@link NetworkStatsHistory} data. Always selects the newest
461 * item, updating the inspection range on {@link #mChart}.
462 */
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700463 private void updateCycleList(NetworkPolicy policy) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700464 mCycleAdapter.clear();
465
466 final Context context = mCycleSpinner.getContext();
467
468 final long[] bounds = getHistoryBounds(mHistory);
469 final long historyStart = bounds[0];
470 final long historyEnd = bounds[1];
471
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700472 if (policy != null) {
473 // find the next cycle boundary
474 long cycleEnd = computeNextCycleBoundary(historyEnd, policy);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700475
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700476 int guardCount = 0;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700477
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700478 // walk backwards, generating all valid cycle ranges
479 while (cycleEnd > historyStart) {
480 final long cycleStart = computeLastCycleBoundary(cycleEnd, policy);
481 Log.d(TAG, "generating cs=" + cycleStart + " to ce=" + cycleEnd + " waiting for hs="
482 + historyStart);
483 mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
484 cycleEnd = cycleStart;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700485
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700486 // TODO: remove this guard once we have better testing
487 if (guardCount++ > 50) {
488 Log.wtf(TAG, "stuck generating ranges for bounds=" + Arrays.toString(bounds)
489 + " and policy=" + policy);
490 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700491 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700492
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700493 // one last cycle entry to modify policy cycle day
494 mCycleAdapter.add(new CycleChangeItem(context));
495
496 } else {
497 // no valid cycle; show all data
498 // TODO: offer simple ranges like "last week" etc
499 mCycleAdapter.add(new CycleItem(context, historyStart, historyEnd));
500
501 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700502
503 // force pick the current cycle (first item)
504 mCycleSpinner.setSelection(0);
505 mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
506 }
507
508 /**
509 * Force rebind of hijacked {@link Preference} views.
510 */
511 private void refreshPreferenceViews() {
512 mDataEnabledView = mDataEnabled.getView(mDataEnabledView, mListView);
513 mDisableAtLimitView = mDisableAtLimit.getView(mDisableAtLimitView, mListView);
514 }
515
516 private OnClickListener mDataEnabledListener = new OnClickListener() {
517 /** {@inheritDoc} */
518 public void onClick(View v) {
519 mDataEnabled.setChecked(!mDataEnabled.isChecked());
520 refreshPreferenceViews();
521
522 // TODO: wire up to telephony to enable/disable radios
523 }
524 };
525
526 private OnClickListener mDisableAtLimitListener = new OnClickListener() {
527 /** {@inheritDoc} */
528 public void onClick(View v) {
529 final boolean disableAtLimit = !mDisableAtLimit.isChecked();
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700530 if (disableAtLimit) {
531 // enabling limit; show confirmation dialog which eventually
532 // calls setPolicyLimitBytes() once user confirms.
533 ConfirmLimitFragment.show(DataUsageSummary.this);
534 } else {
535 setPolicyLimitBytes(LIMIT_DISABLED);
536 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700537 }
538 };
539
540 private OnItemClickListener mListListener = new OnItemClickListener() {
541 /** {@inheritDoc} */
542 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700543 final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700544
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700545 final Bundle args = new Bundle();
546 args.putInt(Intent.EXTRA_UID, app.uid);
547
548 final PreferenceActivity activity = (PreferenceActivity) getActivity();
549 activity.startPreferencePanel(DataUsageAppDetail.class.getName(), args,
550 R.string.data_usage_summary_title, null, null, 0);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700551 }
552 };
553
554 private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() {
555 /** {@inheritDoc} */
556 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
557 final CycleItem cycle = (CycleItem) parent.getItemAtPosition(position);
558 if (cycle instanceof CycleChangeItem) {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700559 // show cycle editor; will eventually call setPolicyCycleDay()
560 // when user finishes editing.
561 CycleEditorFragment.show(DataUsageSummary.this);
562
563 // reset spinner to something other than "change cycle..."
564 mCycleSpinner.setSelection(0);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700565
566 } else {
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700567 if (LOGD) {
568 Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end="
569 + cycle.end + "]");
570 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700571
572 // update chart to show selected cycle, and update detail data
573 // to match updated sweep bounds.
574 final long[] bounds = getHistoryBounds(mHistory);
575 mChart.setVisibleRange(cycle.start, cycle.end, bounds[1]);
576
577 updateDetailData();
578 }
579 }
580
581 /** {@inheritDoc} */
582 public void onNothingSelected(AdapterView<?> parent) {
583 // ignored
584 }
585 };
586
587 /**
588 * Update {@link #mAdapter} with sorted list of applications data usage,
589 * based on current inspection from {@link #mChart}.
590 */
591 private void updateDetailData() {
592 if (LOGD) Log.d(TAG, "updateDetailData()");
593
Jeff Sharkeyaa5260e2011-06-14 23:21:59 -0700594 new AsyncTask<Void, Void, NetworkStats>() {
595 @Override
596 protected NetworkStats doInBackground(Void... params) {
597 try {
598 final long[] range = mChart.getInspectRange();
599 return mStatsService.getSummaryForAllUid(range[0], range[1], mTemplate);
600 } catch (RemoteException e) {
601 Log.w(TAG, "problem reading stats");
602 }
603 return null;
604 }
605
606 @Override
607 protected void onPostExecute(NetworkStats stats) {
608 if (stats != null) {
609 mAdapter.bindStats(stats);
610 }
611 }
612 }.execute();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700613 }
614
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700615 private static String getActiveSubscriberId(Context context) {
616 final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
617 Context.TELEPHONY_SERVICE);
618 return telephony.getSubscriberId();
619 }
620
Jeff Sharkey8a503642011-06-10 13:31:21 -0700621 private DataUsageChartListener mChartListener = new DataUsageChartListener() {
622 /** {@inheritDoc} */
623 public void onInspectRangeChanged() {
624 if (LOGD) Log.d(TAG, "onInspectRangeChanged()");
625 updateDetailData();
626 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700627
Jeff Sharkey8a503642011-06-10 13:31:21 -0700628 /** {@inheritDoc} */
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700629 public void onWarningChanged() {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700630 setPolicyWarningBytes(mChart.getWarningBytes());
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700631 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700632
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700633 /** {@inheritDoc} */
634 public void onLimitChanged() {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700635 setPolicyLimitBytes(mChart.getLimitBytes());
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700636 }
637 };
638
639
640 /**
Jeff Sharkey8a503642011-06-10 13:31:21 -0700641 * List item that reflects a specific data usage cycle.
642 */
643 public static class CycleItem {
644 public CharSequence label;
645 public long start;
646 public long end;
647
648 private static final StringBuilder sBuilder = new StringBuilder(50);
649 private static final java.util.Formatter sFormatter = new java.util.Formatter(
650 sBuilder, Locale.getDefault());
651
652 CycleItem(CharSequence label) {
653 this.label = label;
654 }
655
656 public CycleItem(Context context, long start, long end) {
657 this.label = formatDateRangeUtc(context, start, end);
658 this.start = start;
659 this.end = end;
660 }
661
662 private static String formatDateRangeUtc(Context context, long start, long end) {
663 synchronized (sBuilder) {
664 sBuilder.setLength(0);
665 return DateUtils.formatDateRange(context, sFormatter, start, end,
666 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH,
667 Time.TIMEZONE_UTC).toString();
668 }
669 }
670
671 @Override
672 public String toString() {
673 return label.toString();
674 }
675 }
676
677 /**
678 * Special-case data usage cycle that triggers dialog to change
679 * {@link NetworkPolicy#cycleDay}.
680 */
681 public static class CycleChangeItem extends CycleItem {
682 public CycleChangeItem(Context context) {
683 super(context.getString(R.string.data_usage_change_cycle));
684 }
685 }
686
687 public static class CycleAdapter extends ArrayAdapter<CycleItem> {
688 public CycleAdapter(Context context) {
689 super(context, android.R.layout.simple_spinner_item);
690 setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
691 }
692 }
693
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700694 private static class AppUsageItem implements Comparable<AppUsageItem> {
695 public int uid;
696 public long total;
697
698 /** {@inheritDoc} */
699 public int compareTo(AppUsageItem another) {
700 return Long.compare(another.total, total);
701 }
702 }
703
Jeff Sharkey8a503642011-06-10 13:31:21 -0700704 /**
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700705 * Adapter of applications, sorted by total usage descending.
706 */
707 public static class DataUsageAdapter extends BaseAdapter {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700708 private ArrayList<AppUsageItem> mItems = Lists.newArrayList();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700709
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700710 public void bindStats(NetworkStats stats) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700711 mItems.clear();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700712
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700713 for (int i = 0; i < stats.size; i++) {
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700714 final long total = stats.rx[i] + stats.tx[i];
715 if (total > 0) {
716 final AppUsageItem item = new AppUsageItem();
717 item.uid = stats.uid[i];
718 item.total = total;
719 mItems.add(item);
720 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700721 }
722
Jeff Sharkey8a503642011-06-10 13:31:21 -0700723 Collections.sort(mItems);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700724 notifyDataSetChanged();
725 }
726
727 @Override
728 public int getCount() {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700729 return mItems.size();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700730 }
731
732 @Override
733 public Object getItem(int position) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700734 return mItems.get(position);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700735 }
736
737 @Override
738 public long getItemId(int position) {
739 return position;
740 }
741
742 @Override
743 public View getView(int position, View convertView, ViewGroup parent) {
744 if (convertView == null) {
745 convertView = LayoutInflater.from(parent.getContext()).inflate(
746 android.R.layout.simple_list_item_2, parent, false);
747 }
748
749 final Context context = parent.getContext();
750 final PackageManager pm = context.getPackageManager();
751
752 final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
753 final TextView text2 = (TextView) convertView.findViewById(android.R.id.text2);
754
Jeff Sharkey8a503642011-06-10 13:31:21 -0700755 final AppUsageItem item = mItems.get(position);
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700756 text1.setText(resolveLabelForUid(pm, item.uid));
Jeff Sharkey8a503642011-06-10 13:31:21 -0700757 text2.setText(Formatter.formatFileSize(context, item.total));
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700758
759 return convertView;
760 }
761
762 }
763
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700764 /**
765 * Dialog to request user confirmation before setting
766 * {@link NetworkPolicy#limitBytes}.
767 */
768 public static class ConfirmLimitFragment extends DialogFragment {
769 public static final String EXTRA_MESSAGE_ID = "messageId";
770 public static final String EXTRA_LIMIT_BYTES = "limitBytes";
771
772 public static void show(DataUsageSummary parent) {
773 final Bundle args = new Bundle();
774
775 // TODO: customize default limits based on network template
776 switch (parent.mTemplate) {
777 case TEMPLATE_MOBILE_3G_LOWER: {
778 args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_3g);
779 args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
780 break;
781 }
782 case TEMPLATE_MOBILE_4G: {
783 args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_4g);
784 args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
785 break;
786 }
787 case TEMPLATE_MOBILE_ALL: {
788 args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_mobile);
789 args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
790 break;
791 }
792 }
793
794 final ConfirmLimitFragment dialog = new ConfirmLimitFragment();
795 dialog.setArguments(args);
796 dialog.setTargetFragment(parent, 0);
797 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT);
798 }
799
800 @Override
801 public Dialog onCreateDialog(Bundle savedInstanceState) {
802 final Context context = getActivity();
803
804 final int messageId = getArguments().getInt(EXTRA_MESSAGE_ID);
805 final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES);
806
807 final AlertDialog.Builder builder = new AlertDialog.Builder(context);
808 builder.setTitle(R.string.data_usage_limit_dialog_title);
809 builder.setMessage(messageId);
810
811 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
812 public void onClick(DialogInterface dialog, int which) {
813 final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
814 if (target != null) {
815 target.setPolicyLimitBytes(limitBytes);
816 }
817 }
818 });
819
820 return builder.create();
821 }
822 }
823
824 /**
825 * Dialog to edit {@link NetworkPolicy#cycleDay}.
826 */
827 public static class CycleEditorFragment extends DialogFragment {
828 public static final String EXTRA_CYCLE_DAY = "cycleDay";
829
830 public static void show(DataUsageSummary parent) {
831 final NetworkPolicy policy = parent.mPolicyModifier.getPolicy(parent.mTemplate);
832 final Bundle args = new Bundle();
833 args.putInt(CycleEditorFragment.EXTRA_CYCLE_DAY, policy.cycleDay);
834
835 final CycleEditorFragment dialog = new CycleEditorFragment();
836 dialog.setArguments(args);
837 dialog.setTargetFragment(parent, 0);
838 dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR);
839 }
840
841 @Override
842 public Dialog onCreateDialog(Bundle savedInstanceState) {
843 final Context context = getActivity();
844
845 final AlertDialog.Builder builder = new AlertDialog.Builder(context);
846 final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
847
848 final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
849 final NumberPicker cycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
850
851 final int oldCycleDay = getArguments().getInt(EXTRA_CYCLE_DAY, 1);
852
853 cycleDayPicker.setMinValue(1);
854 cycleDayPicker.setMaxValue(31);
855 cycleDayPicker.setValue(oldCycleDay);
856 cycleDayPicker.setWrapSelectorWheel(true);
857
858 builder.setTitle(R.string.data_usage_cycle_editor_title);
859 builder.setView(view);
860
861 builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
862 new DialogInterface.OnClickListener() {
863 public void onClick(DialogInterface dialog, int which) {
864 final int cycleDay = cycleDayPicker.getValue();
865 final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
866 if (target != null) {
867 target.setPolicyCycleDay(cycleDay);
868 }
869 }
870 });
871
872 return builder.create();
873 }
874 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700875
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700876 /**
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700877 * Dialog explaining that {@link NetworkPolicy#limitBytes} has been passed,
878 * and giving the user an option to bypass.
879 */
880 public static class PolicyLimitFragment extends DialogFragment {
881 public static final String EXTRA_TITLE_ID = "titleId";
882
883 public static void show(DataUsageSummary parent) {
884 final Bundle args = new Bundle();
885
886 switch (parent.mTemplate) {
887 case TEMPLATE_MOBILE_3G_LOWER: {
888 args.putInt(EXTRA_TITLE_ID, R.string.data_usage_disabled_dialog_3g_title);
889 break;
890 }
891 case TEMPLATE_MOBILE_4G: {
892 args.putInt(EXTRA_TITLE_ID, R.string.data_usage_disabled_dialog_4g_title);
893 break;
894 }
895 case TEMPLATE_MOBILE_ALL: {
896 args.putInt(EXTRA_TITLE_ID, R.string.data_usage_disabled_dialog_mobile_title);
897 break;
898 }
899 }
900
901 final PolicyLimitFragment dialog = new PolicyLimitFragment();
902 dialog.setArguments(args);
903 dialog.setTargetFragment(parent, 0);
904 dialog.show(parent.getFragmentManager(), TAG_POLICY_LIMIT);
905 }
906
907 @Override
908 public Dialog onCreateDialog(Bundle savedInstanceState) {
909 final Context context = getActivity();
910
911 final int titleId = getArguments().getInt(EXTRA_TITLE_ID);
912
913 final AlertDialog.Builder builder = new AlertDialog.Builder(context);
914 builder.setTitle(titleId);
915 builder.setMessage(R.string.data_usage_disabled_dialog);
916
917 builder.setPositiveButton(android.R.string.ok, null);
918 builder.setNegativeButton(R.string.data_usage_disabled_dialog_enable,
919 new DialogInterface.OnClickListener() {
920 public void onClick(DialogInterface dialog, int which) {
921 final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
922 if (target != null) {
923 // TODO: consider "allow 100mb more data", or
924 // only bypass limit for current cycle.
925 target.setPolicyLimitBytes(LIMIT_DISABLED);
926 }
927 }
928 });
929
930 return builder.create();
931 }
932 }
933
934 /**
935 * Compute default tab that should be selected, based on
936 * {@link NetworkPolicyManager#EXTRA_NETWORK_TEMPLATE} extra.
937 */
938 private static String computeTabFromIntent(Intent intent) {
939 final int networkTemplate = intent.getIntExtra(EXTRA_NETWORK_TEMPLATE, TEMPLATE_INVALID);
940 switch (networkTemplate) {
941 case TEMPLATE_MOBILE_3G_LOWER:
942 return TAB_3G;
943 case TEMPLATE_MOBILE_4G:
944 return TAB_4G;
945 case TEMPLATE_MOBILE_ALL:
946 return TAB_MOBILE;
947 case TEMPLATE_WIFI:
948 return TAB_WIFI;
949 default:
950 return null;
951 }
952 }
953
954 /**
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700955 * Resolve best descriptive label for the given UID.
956 */
957 public static CharSequence resolveLabelForUid(PackageManager pm, int uid) {
958 final String[] packageNames = pm.getPackagesForUid(uid);
959 final int length = packageNames != null ? packageNames.length : 0;
960
961 CharSequence label = pm.getNameForUid(uid);
962 try {
963 if (length == 1) {
964 final ApplicationInfo info = pm.getApplicationInfo(packageNames[0], 0);
965 label = info.loadLabel(pm);
966 } else if (length > 1) {
967 for (String packageName : packageNames) {
968 final PackageInfo info = pm.getPackageInfo(packageName, 0);
969 if (info.sharedUserLabel != 0) {
970 label = pm.getText(packageName, info.sharedUserLabel, info.applicationInfo);
971 if (!TextUtils.isEmpty(label)) {
972 break;
973 }
974 }
975 }
976 }
977 } catch (NameNotFoundException e) {
978 }
979
980 if (TextUtils.isEmpty(label)) {
981 label = Integer.toString(uid);
982 }
983 return label;
984 }
985
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700986}