blob: 087525c693fc2add2ba862a8bdedd28144e5be1f [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;
Jeff Sharkeydd6efe12011-06-15 10:31:41 -070021import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
Jeff Sharkey8a503642011-06-10 13:31:21 -070022import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
23import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
24import static android.net.TrafficStats.TEMPLATE_MOBILE_3G_LOWER;
25import static android.net.TrafficStats.TEMPLATE_MOBILE_4G;
26import static android.net.TrafficStats.TEMPLATE_MOBILE_ALL;
27import static android.net.TrafficStats.TEMPLATE_WIFI;
28import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070029
Jeff Sharkey4c72ae52011-06-14 15:01:18 -070030import android.app.AlertDialog;
31import android.app.Dialog;
32import android.app.DialogFragment;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070033import android.app.Fragment;
34import android.content.Context;
Jeff Sharkey4c72ae52011-06-14 15:01:18 -070035import android.content.DialogInterface;
Jeff Sharkey4dfa6602011-06-13 00:42:03 -070036import android.content.Intent;
Jeff Sharkey8e911d72011-06-14 22:41:21 -070037import android.content.pm.ApplicationInfo;
38import android.content.pm.PackageInfo;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070039import android.content.pm.PackageManager;
Jeff Sharkey8e911d72011-06-14 22:41:21 -070040import android.content.pm.PackageManager.NameNotFoundException;
Jeff Sharkey8a503642011-06-10 13:31:21 -070041import android.net.INetworkPolicyManager;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070042import android.net.INetworkStatsService;
Jeff Sharkey8a503642011-06-10 13:31:21 -070043import android.net.NetworkPolicy;
Jeff Sharkeydd6efe12011-06-15 10:31:41 -070044import android.net.NetworkPolicyManager;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070045import android.net.NetworkStats;
46import android.net.NetworkStatsHistory;
Jeff Sharkeyaa5260e2011-06-14 23:21:59 -070047import android.os.AsyncTask;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070048import android.os.Bundle;
49import android.os.RemoteException;
50import android.os.ServiceManager;
Jeff Sharkey8a503642011-06-10 13:31:21 -070051import android.preference.CheckBoxPreference;
52import android.preference.Preference;
Jeff Sharkey4dfa6602011-06-13 00:42:03 -070053import android.preference.PreferenceActivity;
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -070054import android.preference.SwitchPreference;
55import android.telephony.TelephonyManager;
Jeff Sharkey8e911d72011-06-14 22:41:21 -070056import android.text.TextUtils;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070057import android.text.format.DateUtils;
58import android.text.format.Formatter;
Jeff Sharkey8a503642011-06-10 13:31:21 -070059import android.text.format.Time;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070060import android.util.Log;
61import android.view.LayoutInflater;
Jeff Sharkey8a503642011-06-10 13:31:21 -070062import android.view.Menu;
63import android.view.MenuInflater;
64import android.view.MenuItem;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070065import android.view.View;
Jeff Sharkey8a503642011-06-10 13:31:21 -070066import android.view.View.OnClickListener;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070067import android.view.ViewGroup;
Jeff Sharkey8a503642011-06-10 13:31:21 -070068import android.widget.AbsListView;
69import android.widget.AdapterView;
70import android.widget.AdapterView.OnItemClickListener;
71import android.widget.AdapterView.OnItemSelectedListener;
72import android.widget.ArrayAdapter;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070073import android.widget.BaseAdapter;
Jeff Sharkey8a503642011-06-10 13:31:21 -070074import android.widget.LinearLayout;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070075import android.widget.ListView;
Jeff Sharkey4c72ae52011-06-14 15:01:18 -070076import android.widget.NumberPicker;
Jeff Sharkey8a503642011-06-10 13:31:21 -070077import android.widget.Spinner;
78import android.widget.TabHost;
79import android.widget.TabHost.OnTabChangeListener;
80import android.widget.TabHost.TabContentFactory;
81import android.widget.TabHost.TabSpec;
82import android.widget.TabWidget;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070083import android.widget.TextView;
84
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -070085import com.android.settings.net.NetworkPolicyModifier;
Jeff Sharkey8a503642011-06-10 13:31:21 -070086import com.android.settings.widget.DataUsageChartView;
87import com.android.settings.widget.DataUsageChartView.DataUsageChartListener;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070088import com.google.android.collect.Lists;
89
90import java.util.ArrayList;
Jeff Sharkey8a503642011-06-10 13:31:21 -070091import java.util.Arrays;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070092import java.util.Collections;
Jeff Sharkey8a503642011-06-10 13:31:21 -070093import java.util.Locale;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070094
95public class DataUsageSummary extends Fragment {
96 private static final String TAG = "DataUsage";
Jeff Sharkey8a503642011-06-10 13:31:21 -070097 private static final boolean LOGD = true;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -070098
Jeff Sharkey8a503642011-06-10 13:31:21 -070099 private static final int TEMPLATE_INVALID = -1;
100
101 private static final String TAB_3G = "3g";
102 private static final String TAB_4G = "4g";
103 private static final String TAB_MOBILE = "mobile";
104 private static final String TAB_WIFI = "wifi";
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700105
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700106 private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
107 private static final String TAG_CYCLE_EDITOR = "cycleEditor";
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700108 private static final String TAG_POLICY_LIMIT = "policyLimit";
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700109
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700110 private static final long KB_IN_BYTES = 1024;
111 private static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
112 private static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
113
114 private INetworkStatsService mStatsService;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700115 private INetworkPolicyManager mPolicyService;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700116
Jeff Sharkey8a503642011-06-10 13:31:21 -0700117 private TabHost mTabHost;
118 private TabWidget mTabWidget;
119 private ListView mListView;
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700120 private DataUsageAdapter mAdapter;
121
Jeff Sharkey8a503642011-06-10 13:31:21 -0700122 private View mHeader;
123 private LinearLayout mSwitches;
124
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700125 private SwitchPreference mDataEnabled;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700126 private CheckBoxPreference mDisableAtLimit;
127 private View mDataEnabledView;
128 private View mDisableAtLimitView;
129
130 private DataUsageChartView mChart;
131
132 private Spinner mCycleSpinner;
133 private CycleAdapter mCycleAdapter;
134
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700135 // TODO: persist show wifi flag
Jeff Sharkey8a503642011-06-10 13:31:21 -0700136 private boolean mShowWifi = false;
137
138 private int mTemplate = TEMPLATE_INVALID;
139
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700140 private NetworkPolicyModifier mPolicyModifier;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700141 private NetworkStatsHistory mHistory;
142
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700143 private String mIntentTab = null;
144
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700145 @Override
Jeff Sharkey8a503642011-06-10 13:31:21 -0700146 public void onCreate(Bundle savedInstanceState) {
147 super.onCreate(savedInstanceState);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700148
149 mStatsService = INetworkStatsService.Stub.asInterface(
150 ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
Jeff Sharkey8a503642011-06-10 13:31:21 -0700151 mPolicyService = INetworkPolicyManager.Stub.asInterface(
152 ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700153
154 final Context context = getActivity();
155 final String subscriberId = getActiveSubscriberId(context);
156 mPolicyModifier = new NetworkPolicyModifier(mPolicyService, subscriberId);
Jeff Sharkey94a90952011-06-13 22:31:09 -0700157 mPolicyModifier.read();
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700158
159 setHasOptionsMenu(true);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700160 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700161
Jeff Sharkey8a503642011-06-10 13:31:21 -0700162 @Override
163 public View onCreateView(LayoutInflater inflater, ViewGroup container,
164 Bundle savedInstanceState) {
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700165
Jeff Sharkey8a503642011-06-10 13:31:21 -0700166 final Context context = inflater.getContext();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700167 final View view = inflater.inflate(R.layout.data_usage_summary, container, false);
168
Jeff Sharkey8a503642011-06-10 13:31:21 -0700169 mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
170 mTabWidget = (TabWidget) view.findViewById(android.R.id.tabs);
171 mListView = (ListView) view.findViewById(android.R.id.list);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700172
Jeff Sharkey8a503642011-06-10 13:31:21 -0700173 mTabHost.setup();
174 mTabHost.setOnTabChangedListener(mTabListener);
175
176 mHeader = inflater.inflate(R.layout.data_usage_header, mListView, false);
177 mListView.addHeaderView(mHeader, null, false);
178
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700179 mDataEnabled = new SwitchPreference(context);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700180 mDisableAtLimit = new CheckBoxPreference(context);
181
182 // kick refresh once to force-create views
183 refreshPreferenceViews();
184
185 // TODO: remove once thin preferences are supported (48dip)
186 mDataEnabledView.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 72));
187 mDisableAtLimitView.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 72));
188
189 mDataEnabledView.setOnClickListener(mDataEnabledListener);
190 mDisableAtLimitView.setOnClickListener(mDisableAtLimitListener);
191
192 mSwitches = (LinearLayout) mHeader.findViewById(R.id.switches);
193 mSwitches.addView(mDataEnabledView);
194 mSwitches.addView(mDisableAtLimitView);
195
196 mCycleSpinner = (Spinner) mHeader.findViewById(R.id.cycles);
197 mCycleAdapter = new CycleAdapter(context);
198 mCycleSpinner.setAdapter(mCycleAdapter);
199 mCycleSpinner.setOnItemSelectedListener(mCycleListener);
200
201 mChart = new DataUsageChartView(context);
202 mChart.setListener(mChartListener);
203 mChart.setLayoutParams(new AbsListView.LayoutParams(MATCH_PARENT, 350));
204 mListView.addHeaderView(mChart, null, false);
205
206 mAdapter = new DataUsageAdapter();
207 mListView.setOnItemClickListener(mListListener);
208 mListView.setAdapter(mAdapter);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700209
210 return view;
211 }
212
213 @Override
214 public void onResume() {
215 super.onResume();
216
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700217 // pick default tab based on incoming intent
218 final Intent intent = getActivity().getIntent();
219 mIntentTab = computeTabFromIntent(intent);
220
Jeff Sharkey8a503642011-06-10 13:31:21 -0700221 // this kicks off chain reaction which creates tabs, binds the body to
222 // selected network, and binds chart, cycles and detail list.
223 updateTabs();
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700224
225 // template and tab has been selected; show dialog if limit passed
226 final String action = intent.getAction();
227 if (ACTION_DATA_USAGE_LIMIT.equals(action)) {
228 PolicyLimitFragment.show(this);
229 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700230 }
231
Jeff Sharkey8a503642011-06-10 13:31:21 -0700232 @Override
233 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
234 inflater.inflate(R.menu.data_usage, menu);
235 }
236
237 @Override
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700238 public void onPrepareOptionsMenu(Menu menu) {
239 final MenuItem split4g = menu.findItem(R.id.action_split_4g);
240 split4g.setChecked(mPolicyModifier.isMobilePolicySplit());
241 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700242
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700243 @Override
244 public boolean onOptionsItemSelected(MenuItem item) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700245 switch (item.getItemId()) {
246 case R.id.action_split_4g: {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700247 final boolean mobileSplit = !item.isChecked();
248 mPolicyModifier.setMobilePolicySplit(mobileSplit);
249 item.setChecked(mPolicyModifier.isMobilePolicySplit());
Jeff Sharkey8a503642011-06-10 13:31:21 -0700250 updateTabs();
251 return true;
252 }
253 case R.id.action_show_wifi: {
254 mShowWifi = !item.isChecked();
255 item.setChecked(mShowWifi);
256 updateTabs();
257 return true;
258 }
259 }
260 return false;
261 }
262
Jeff Sharkey94a90952011-06-13 22:31:09 -0700263 @Override
264 public void onDestroyView() {
265 super.onDestroyView();
266
267 mDataEnabledView = null;
268 mDisableAtLimitView = null;
269 }
270
Jeff Sharkey8a503642011-06-10 13:31:21 -0700271 /**
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700272 * Rebuild all tabs based on {@link NetworkPolicyModifier} and
273 * {@link #mShowWifi}, hiding the tabs entirely when applicable. Selects
274 * first tab, and kicks off a full rebind of body contents.
Jeff Sharkey8a503642011-06-10 13:31:21 -0700275 */
276 private void updateTabs() {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700277 final boolean mobileSplit = mPolicyModifier.isMobilePolicySplit();
278 final boolean tabsVisible = mobileSplit || mShowWifi;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700279 mTabWidget.setVisibility(tabsVisible ? View.VISIBLE : View.GONE);
280 mTabHost.clearAllTabs();
281
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700282 if (mobileSplit) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700283 mTabHost.addTab(buildTabSpec(TAB_3G, R.string.data_usage_tab_3g));
284 mTabHost.addTab(buildTabSpec(TAB_4G, R.string.data_usage_tab_4g));
285 }
286
287 if (mShowWifi) {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700288 if (!mobileSplit) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700289 mTabHost.addTab(buildTabSpec(TAB_MOBILE, R.string.data_usage_tab_mobile));
290 }
291 mTabHost.addTab(buildTabSpec(TAB_WIFI, R.string.data_usage_tab_wifi));
292 }
293
294 if (mTabWidget.getTabCount() > 0) {
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700295 if (mIntentTab != null) {
296 // select default tab, which will kick off updateBody()
297 mTabHost.setCurrentTabByTag(mIntentTab);
298 } else {
299 // select first tab, which will kick off updateBody()
300 mTabHost.setCurrentTab(0);
301 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700302 } else {
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700303 // no tabs visible; update body manually
Jeff Sharkey8a503642011-06-10 13:31:21 -0700304 updateBody();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700305 }
306 }
307
Jeff Sharkey8a503642011-06-10 13:31:21 -0700308 /**
309 * Factory that provide empty {@link View} to make {@link TabHost} happy.
310 */
311 private TabContentFactory mEmptyTabContent = new TabContentFactory() {
312 /** {@inheritDoc} */
313 public View createTabContent(String tag) {
314 return new View(mTabHost.getContext());
315 }
316 };
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700317
Jeff Sharkey8a503642011-06-10 13:31:21 -0700318 /**
319 * Build {@link TabSpec} with thin indicator, and empty content.
320 */
321 private TabSpec buildTabSpec(String tag, int titleRes) {
322 final LayoutInflater inflater = LayoutInflater.from(mTabWidget.getContext());
323 final View indicator = inflater.inflate(
324 R.layout.tab_indicator_thin_holo, mTabWidget, false);
325 final TextView title = (TextView) indicator.findViewById(android.R.id.title);
326 title.setText(titleRes);
327 return mTabHost.newTabSpec(tag).setIndicator(indicator).setContent(mEmptyTabContent);
328 }
329
330 private OnTabChangeListener mTabListener = new OnTabChangeListener() {
331 /** {@inheritDoc} */
332 public void onTabChanged(String tabId) {
333 // user changed tab; update body
334 updateBody();
335 }
336 };
337
338 /**
339 * Update body content based on current tab. Loads
340 * {@link NetworkStatsHistory} and {@link NetworkPolicy} from system, and
341 * binds them to visible controls.
342 */
343 private void updateBody() {
344 final String tabTag = mTabHost.getCurrentTabTag();
345 final String currentTab = tabTag != null ? tabTag : TAB_MOBILE;
346
347 if (LOGD) Log.d(TAG, "updateBody() with currentTab=" + currentTab);
348
349 if (TAB_WIFI.equals(currentTab)) {
350 // wifi doesn't have any controls
351 mDataEnabledView.setVisibility(View.GONE);
352 mDisableAtLimitView.setVisibility(View.GONE);
353 mTemplate = TEMPLATE_WIFI;
354
355 } else {
356 // make sure we show for non-wifi
357 mDataEnabledView.setVisibility(View.VISIBLE);
358 mDisableAtLimitView.setVisibility(View.VISIBLE);
359 }
360
361 if (TAB_MOBILE.equals(currentTab)) {
362 mDataEnabled.setTitle(R.string.data_usage_enable_mobile);
363 mDisableAtLimit.setTitle(R.string.data_usage_disable_mobile_limit);
364 mTemplate = TEMPLATE_MOBILE_ALL;
365
366 } else if (TAB_3G.equals(currentTab)) {
367 mDataEnabled.setTitle(R.string.data_usage_enable_3g);
368 mDisableAtLimit.setTitle(R.string.data_usage_disable_3g_limit);
369 mTemplate = TEMPLATE_MOBILE_3G_LOWER;
370
371 } else if (TAB_4G.equals(currentTab)) {
372 mDataEnabled.setTitle(R.string.data_usage_enable_4g);
373 mDisableAtLimit.setTitle(R.string.data_usage_disable_4g_limit);
374 mTemplate = TEMPLATE_MOBILE_4G;
375
376 }
377
378 // TODO: populate checkbox based on radio preferences
379 mDataEnabled.setChecked(true);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700380
381 try {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700382 // load stats for current template
Jeff Sharkey8a503642011-06-10 13:31:21 -0700383 mHistory = mStatsService.getHistoryForNetwork(mTemplate);
384 } catch (RemoteException e) {
385 // since we can't do much without policy or history, and we don't
386 // want to leave with half-baked UI, we bail hard.
387 throw new RuntimeException("problem reading network policy or stats", e);
388 }
389
Jeff Sharkey8a503642011-06-10 13:31:21 -0700390 // bind chart to historical stats
Jeff Sharkey8a503642011-06-10 13:31:21 -0700391 mChart.bindNetworkStats(mHistory);
392
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700393 updatePolicy(true);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700394
395 // force scroll to top of body
396 mListView.smoothScrollToPosition(0);
397
398 // kick preference views so they rebind from changes above
399 refreshPreferenceViews();
400 }
401
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700402 private void setPolicyCycleDay(int cycleDay) {
403 if (LOGD) Log.d(TAG, "setPolicyCycleDay()");
404 mPolicyModifier.setPolicyCycleDay(mTemplate, cycleDay);
405 updatePolicy(true);
406 }
407
408 private void setPolicyWarningBytes(long warningBytes) {
409 if (LOGD) Log.d(TAG, "setPolicyWarningBytes()");
410 mPolicyModifier.setPolicyWarningBytes(mTemplate, warningBytes);
411 updatePolicy(false);
412 }
413
414 private void setPolicyLimitBytes(long limitBytes) {
415 if (LOGD) Log.d(TAG, "setPolicyLimitBytes()");
416 mPolicyModifier.setPolicyLimitBytes(mTemplate, limitBytes);
417 updatePolicy(false);
418 }
419
Jeff Sharkey8a503642011-06-10 13:31:21 -0700420 /**
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700421 * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for
422 * current {@link #mTemplate}.
423 */
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700424 private void updatePolicy(boolean refreshCycle) {
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700425 final NetworkPolicy policy = mPolicyModifier.getPolicy(mTemplate);
426
427 // reflect policy limit in checkbox
428 mDisableAtLimit.setChecked(policy != null && policy.limitBytes != LIMIT_DISABLED);
429 mChart.bindNetworkPolicy(policy);
430
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700431 if (refreshCycle) {
432 // generate cycle list based on policy and available history
433 updateCycleList(policy);
434 }
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700435
436 // kick preference views so they rebind from changes above
437 refreshPreferenceViews();
438 }
439
440 /**
Jeff Sharkey8a503642011-06-10 13:31:21 -0700441 * Return full time bounds (earliest and latest time recorded) of the given
442 * {@link NetworkStatsHistory}.
443 */
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700444 public static long[] getHistoryBounds(NetworkStatsHistory history) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700445 final long currentTime = System.currentTimeMillis();
446
447 long start = currentTime;
448 long end = currentTime;
449 if (history.bucketCount > 0) {
450 start = history.bucketStart[0];
451 end = history.bucketStart[history.bucketCount - 1];
452 }
453
454 return new long[] { start, end };
455 }
456
457 /**
458 * Rebuild {@link #mCycleAdapter} based on {@link NetworkPolicy#cycleDay}
459 * and available {@link NetworkStatsHistory} data. Always selects the newest
460 * item, updating the inspection range on {@link #mChart}.
461 */
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700462 private void updateCycleList(NetworkPolicy policy) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700463 mCycleAdapter.clear();
464
465 final Context context = mCycleSpinner.getContext();
466
467 final long[] bounds = getHistoryBounds(mHistory);
468 final long historyStart = bounds[0];
469 final long historyEnd = bounds[1];
470
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700471 if (policy != null) {
472 // find the next cycle boundary
473 long cycleEnd = computeNextCycleBoundary(historyEnd, policy);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700474
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700475 int guardCount = 0;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700476
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700477 // walk backwards, generating all valid cycle ranges
478 while (cycleEnd > historyStart) {
479 final long cycleStart = computeLastCycleBoundary(cycleEnd, policy);
480 Log.d(TAG, "generating cs=" + cycleStart + " to ce=" + cycleEnd + " waiting for hs="
481 + historyStart);
482 mCycleAdapter.add(new CycleItem(context, cycleStart, cycleEnd));
483 cycleEnd = cycleStart;
Jeff Sharkey8a503642011-06-10 13:31:21 -0700484
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700485 // TODO: remove this guard once we have better testing
486 if (guardCount++ > 50) {
487 Log.wtf(TAG, "stuck generating ranges for bounds=" + Arrays.toString(bounds)
488 + " and policy=" + policy);
489 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700490 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700491
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700492 // one last cycle entry to modify policy cycle day
493 mCycleAdapter.add(new CycleChangeItem(context));
494
495 } else {
496 // no valid cycle; show all data
497 // TODO: offer simple ranges like "last week" etc
498 mCycleAdapter.add(new CycleItem(context, historyStart, historyEnd));
499
500 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700501
502 // force pick the current cycle (first item)
503 mCycleSpinner.setSelection(0);
504 mCycleListener.onItemSelected(mCycleSpinner, null, 0, 0);
505 }
506
507 /**
508 * Force rebind of hijacked {@link Preference} views.
509 */
510 private void refreshPreferenceViews() {
511 mDataEnabledView = mDataEnabled.getView(mDataEnabledView, mListView);
512 mDisableAtLimitView = mDisableAtLimit.getView(mDisableAtLimitView, mListView);
513 }
514
515 private OnClickListener mDataEnabledListener = new OnClickListener() {
516 /** {@inheritDoc} */
517 public void onClick(View v) {
518 mDataEnabled.setChecked(!mDataEnabled.isChecked());
519 refreshPreferenceViews();
520
521 // TODO: wire up to telephony to enable/disable radios
522 }
523 };
524
525 private OnClickListener mDisableAtLimitListener = new OnClickListener() {
526 /** {@inheritDoc} */
527 public void onClick(View v) {
528 final boolean disableAtLimit = !mDisableAtLimit.isChecked();
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700529 if (disableAtLimit) {
530 // enabling limit; show confirmation dialog which eventually
531 // calls setPolicyLimitBytes() once user confirms.
532 ConfirmLimitFragment.show(DataUsageSummary.this);
533 } else {
534 setPolicyLimitBytes(LIMIT_DISABLED);
535 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700536 }
537 };
538
539 private OnItemClickListener mListListener = new OnItemClickListener() {
540 /** {@inheritDoc} */
541 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700542 final AppUsageItem app = (AppUsageItem) parent.getItemAtPosition(position);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700543
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700544 final Bundle args = new Bundle();
545 args.putInt(Intent.EXTRA_UID, app.uid);
546
547 final PreferenceActivity activity = (PreferenceActivity) getActivity();
548 activity.startPreferencePanel(DataUsageAppDetail.class.getName(), args,
549 R.string.data_usage_summary_title, null, null, 0);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700550 }
551 };
552
553 private OnItemSelectedListener mCycleListener = new OnItemSelectedListener() {
554 /** {@inheritDoc} */
555 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
556 final CycleItem cycle = (CycleItem) parent.getItemAtPosition(position);
557 if (cycle instanceof CycleChangeItem) {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700558 // show cycle editor; will eventually call setPolicyCycleDay()
559 // when user finishes editing.
560 CycleEditorFragment.show(DataUsageSummary.this);
561
562 // reset spinner to something other than "change cycle..."
563 mCycleSpinner.setSelection(0);
Jeff Sharkey8a503642011-06-10 13:31:21 -0700564
565 } else {
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700566 if (LOGD) {
567 Log.d(TAG, "showing cycle " + cycle + ", start=" + cycle.start + ", end="
568 + cycle.end + "]");
569 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700570
571 // update chart to show selected cycle, and update detail data
572 // to match updated sweep bounds.
573 final long[] bounds = getHistoryBounds(mHistory);
574 mChart.setVisibleRange(cycle.start, cycle.end, bounds[1]);
575
576 updateDetailData();
577 }
578 }
579
580 /** {@inheritDoc} */
581 public void onNothingSelected(AdapterView<?> parent) {
582 // ignored
583 }
584 };
585
586 /**
587 * Update {@link #mAdapter} with sorted list of applications data usage,
588 * based on current inspection from {@link #mChart}.
589 */
590 private void updateDetailData() {
591 if (LOGD) Log.d(TAG, "updateDetailData()");
592
Jeff Sharkeyaa5260e2011-06-14 23:21:59 -0700593 new AsyncTask<Void, Void, NetworkStats>() {
594 @Override
595 protected NetworkStats doInBackground(Void... params) {
596 try {
597 final long[] range = mChart.getInspectRange();
598 return mStatsService.getSummaryForAllUid(range[0], range[1], mTemplate);
599 } catch (RemoteException e) {
600 Log.w(TAG, "problem reading stats");
601 }
602 return null;
603 }
604
605 @Override
606 protected void onPostExecute(NetworkStats stats) {
607 if (stats != null) {
608 mAdapter.bindStats(stats);
609 }
610 }
611 }.execute();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700612 }
613
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700614 private static String getActiveSubscriberId(Context context) {
615 final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
616 Context.TELEPHONY_SERVICE);
617 return telephony.getSubscriberId();
618 }
619
Jeff Sharkey8a503642011-06-10 13:31:21 -0700620 private DataUsageChartListener mChartListener = new DataUsageChartListener() {
621 /** {@inheritDoc} */
622 public void onInspectRangeChanged() {
623 if (LOGD) Log.d(TAG, "onInspectRangeChanged()");
624 updateDetailData();
625 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700626
Jeff Sharkey8a503642011-06-10 13:31:21 -0700627 /** {@inheritDoc} */
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700628 public void onWarningChanged() {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700629 setPolicyWarningBytes(mChart.getWarningBytes());
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700630 }
Jeff Sharkey8a503642011-06-10 13:31:21 -0700631
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700632 /** {@inheritDoc} */
633 public void onLimitChanged() {
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700634 setPolicyLimitBytes(mChart.getLimitBytes());
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700635 }
636 };
637
638
639 /**
Jeff Sharkey8a503642011-06-10 13:31:21 -0700640 * List item that reflects a specific data usage cycle.
641 */
642 public static class CycleItem {
643 public CharSequence label;
644 public long start;
645 public long end;
646
647 private static final StringBuilder sBuilder = new StringBuilder(50);
648 private static final java.util.Formatter sFormatter = new java.util.Formatter(
649 sBuilder, Locale.getDefault());
650
651 CycleItem(CharSequence label) {
652 this.label = label;
653 }
654
655 public CycleItem(Context context, long start, long end) {
656 this.label = formatDateRangeUtc(context, start, end);
657 this.start = start;
658 this.end = end;
659 }
660
661 private static String formatDateRangeUtc(Context context, long start, long end) {
662 synchronized (sBuilder) {
663 sBuilder.setLength(0);
664 return DateUtils.formatDateRange(context, sFormatter, start, end,
665 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_ABBREV_MONTH,
666 Time.TIMEZONE_UTC).toString();
667 }
668 }
669
670 @Override
671 public String toString() {
672 return label.toString();
673 }
674 }
675
676 /**
677 * Special-case data usage cycle that triggers dialog to change
678 * {@link NetworkPolicy#cycleDay}.
679 */
680 public static class CycleChangeItem extends CycleItem {
681 public CycleChangeItem(Context context) {
682 super(context.getString(R.string.data_usage_change_cycle));
683 }
684 }
685
686 public static class CycleAdapter extends ArrayAdapter<CycleItem> {
687 public CycleAdapter(Context context) {
688 super(context, android.R.layout.simple_spinner_item);
689 setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
690 }
691 }
692
Jeff Sharkey4dfa6602011-06-13 00:42:03 -0700693 private static class AppUsageItem implements Comparable<AppUsageItem> {
694 public int uid;
695 public long total;
696
697 /** {@inheritDoc} */
698 public int compareTo(AppUsageItem another) {
699 return Long.compare(another.total, total);
700 }
701 }
702
Jeff Sharkey8a503642011-06-10 13:31:21 -0700703 /**
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700704 * Adapter of applications, sorted by total usage descending.
705 */
706 public static class DataUsageAdapter extends BaseAdapter {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700707 private ArrayList<AppUsageItem> mItems = Lists.newArrayList();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700708
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700709 public void bindStats(NetworkStats stats) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700710 mItems.clear();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700711
Jeff Sharkey05cc0cc2011-06-12 23:11:24 -0700712 for (int i = 0; i < stats.size; i++) {
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700713 final long total = stats.rx[i] + stats.tx[i];
714 if (total > 0) {
715 final AppUsageItem item = new AppUsageItem();
716 item.uid = stats.uid[i];
717 item.total = total;
718 mItems.add(item);
719 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700720 }
721
Jeff Sharkey8a503642011-06-10 13:31:21 -0700722 Collections.sort(mItems);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700723 notifyDataSetChanged();
724 }
725
726 @Override
727 public int getCount() {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700728 return mItems.size();
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700729 }
730
731 @Override
732 public Object getItem(int position) {
Jeff Sharkey8a503642011-06-10 13:31:21 -0700733 return mItems.get(position);
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700734 }
735
736 @Override
737 public long getItemId(int position) {
738 return position;
739 }
740
741 @Override
742 public View getView(int position, View convertView, ViewGroup parent) {
743 if (convertView == null) {
744 convertView = LayoutInflater.from(parent.getContext()).inflate(
745 android.R.layout.simple_list_item_2, parent, false);
746 }
747
748 final Context context = parent.getContext();
749 final PackageManager pm = context.getPackageManager();
750
751 final TextView text1 = (TextView) convertView.findViewById(android.R.id.text1);
752 final TextView text2 = (TextView) convertView.findViewById(android.R.id.text2);
753
Jeff Sharkey8a503642011-06-10 13:31:21 -0700754 final AppUsageItem item = mItems.get(position);
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700755 text1.setText(resolveLabelForUid(pm, item.uid));
Jeff Sharkey8a503642011-06-10 13:31:21 -0700756 text2.setText(Formatter.formatFileSize(context, item.total));
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700757
758 return convertView;
759 }
760
761 }
762
Jeff Sharkey4c72ae52011-06-14 15:01:18 -0700763 /**
764 * Dialog to request user confirmation before setting
765 * {@link NetworkPolicy#limitBytes}.
766 */
767 public static class ConfirmLimitFragment extends DialogFragment {
768 public static final String EXTRA_MESSAGE_ID = "messageId";
769 public static final String EXTRA_LIMIT_BYTES = "limitBytes";
770
771 public static void show(DataUsageSummary parent) {
772 final Bundle args = new Bundle();
773
774 // TODO: customize default limits based on network template
775 switch (parent.mTemplate) {
776 case TEMPLATE_MOBILE_3G_LOWER: {
777 args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_3g);
778 args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
779 break;
780 }
781 case TEMPLATE_MOBILE_4G: {
782 args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_4g);
783 args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
784 break;
785 }
786 case TEMPLATE_MOBILE_ALL: {
787 args.putInt(EXTRA_MESSAGE_ID, R.string.data_usage_limit_dialog_mobile);
788 args.putLong(EXTRA_LIMIT_BYTES, 5 * GB_IN_BYTES);
789 break;
790 }
791 }
792
793 final ConfirmLimitFragment dialog = new ConfirmLimitFragment();
794 dialog.setArguments(args);
795 dialog.setTargetFragment(parent, 0);
796 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_LIMIT);
797 }
798
799 @Override
800 public Dialog onCreateDialog(Bundle savedInstanceState) {
801 final Context context = getActivity();
802
803 final int messageId = getArguments().getInt(EXTRA_MESSAGE_ID);
804 final long limitBytes = getArguments().getLong(EXTRA_LIMIT_BYTES);
805
806 final AlertDialog.Builder builder = new AlertDialog.Builder(context);
807 builder.setTitle(R.string.data_usage_limit_dialog_title);
808 builder.setMessage(messageId);
809
810 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
811 public void onClick(DialogInterface dialog, int which) {
812 final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
813 if (target != null) {
814 target.setPolicyLimitBytes(limitBytes);
815 }
816 }
817 });
818
819 return builder.create();
820 }
821 }
822
823 /**
824 * Dialog to edit {@link NetworkPolicy#cycleDay}.
825 */
826 public static class CycleEditorFragment extends DialogFragment {
827 public static final String EXTRA_CYCLE_DAY = "cycleDay";
828
829 public static void show(DataUsageSummary parent) {
830 final NetworkPolicy policy = parent.mPolicyModifier.getPolicy(parent.mTemplate);
831 final Bundle args = new Bundle();
832 args.putInt(CycleEditorFragment.EXTRA_CYCLE_DAY, policy.cycleDay);
833
834 final CycleEditorFragment dialog = new CycleEditorFragment();
835 dialog.setArguments(args);
836 dialog.setTargetFragment(parent, 0);
837 dialog.show(parent.getFragmentManager(), TAG_CYCLE_EDITOR);
838 }
839
840 @Override
841 public Dialog onCreateDialog(Bundle savedInstanceState) {
842 final Context context = getActivity();
843
844 final AlertDialog.Builder builder = new AlertDialog.Builder(context);
845 final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
846
847 final View view = dialogInflater.inflate(R.layout.data_usage_cycle_editor, null, false);
848 final NumberPicker cycleDayPicker = (NumberPicker) view.findViewById(R.id.cycle_day);
849
850 final int oldCycleDay = getArguments().getInt(EXTRA_CYCLE_DAY, 1);
851
852 cycleDayPicker.setMinValue(1);
853 cycleDayPicker.setMaxValue(31);
854 cycleDayPicker.setValue(oldCycleDay);
855 cycleDayPicker.setWrapSelectorWheel(true);
856
857 builder.setTitle(R.string.data_usage_cycle_editor_title);
858 builder.setView(view);
859
860 builder.setPositiveButton(R.string.data_usage_cycle_editor_positive,
861 new DialogInterface.OnClickListener() {
862 public void onClick(DialogInterface dialog, int which) {
863 final int cycleDay = cycleDayPicker.getValue();
864 final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
865 if (target != null) {
866 target.setPolicyCycleDay(cycleDay);
867 }
868 }
869 });
870
871 return builder.create();
872 }
873 }
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700874
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700875 /**
Jeff Sharkeydd6efe12011-06-15 10:31:41 -0700876 * Dialog explaining that {@link NetworkPolicy#limitBytes} has been passed,
877 * and giving the user an option to bypass.
878 */
879 public static class PolicyLimitFragment extends DialogFragment {
880 public static final String EXTRA_TITLE_ID = "titleId";
881
882 public static void show(DataUsageSummary parent) {
883 final Bundle args = new Bundle();
884
885 switch (parent.mTemplate) {
886 case TEMPLATE_MOBILE_3G_LOWER: {
887 args.putInt(EXTRA_TITLE_ID, R.string.data_usage_disabled_dialog_3g_title);
888 break;
889 }
890 case TEMPLATE_MOBILE_4G: {
891 args.putInt(EXTRA_TITLE_ID, R.string.data_usage_disabled_dialog_4g_title);
892 break;
893 }
894 case TEMPLATE_MOBILE_ALL: {
895 args.putInt(EXTRA_TITLE_ID, R.string.data_usage_disabled_dialog_mobile_title);
896 break;
897 }
898 }
899
900 final PolicyLimitFragment dialog = new PolicyLimitFragment();
901 dialog.setArguments(args);
902 dialog.setTargetFragment(parent, 0);
903 dialog.show(parent.getFragmentManager(), TAG_POLICY_LIMIT);
904 }
905
906 @Override
907 public Dialog onCreateDialog(Bundle savedInstanceState) {
908 final Context context = getActivity();
909
910 final int titleId = getArguments().getInt(EXTRA_TITLE_ID);
911
912 final AlertDialog.Builder builder = new AlertDialog.Builder(context);
913 builder.setTitle(titleId);
914 builder.setMessage(R.string.data_usage_disabled_dialog);
915
916 builder.setPositiveButton(android.R.string.ok, null);
917 builder.setNegativeButton(R.string.data_usage_disabled_dialog_enable,
918 new DialogInterface.OnClickListener() {
919 public void onClick(DialogInterface dialog, int which) {
920 final DataUsageSummary target = (DataUsageSummary) getTargetFragment();
921 if (target != null) {
922 // TODO: consider "allow 100mb more data", or
923 // only bypass limit for current cycle.
924 target.setPolicyLimitBytes(LIMIT_DISABLED);
925 }
926 }
927 });
928
929 return builder.create();
930 }
931 }
932
933 /**
934 * Compute default tab that should be selected, based on
935 * {@link NetworkPolicyManager#EXTRA_NETWORK_TEMPLATE} extra.
936 */
937 private static String computeTabFromIntent(Intent intent) {
938 final int networkTemplate = intent.getIntExtra(EXTRA_NETWORK_TEMPLATE, TEMPLATE_INVALID);
939 switch (networkTemplate) {
940 case TEMPLATE_MOBILE_3G_LOWER:
941 return TAB_3G;
942 case TEMPLATE_MOBILE_4G:
943 return TAB_4G;
944 case TEMPLATE_MOBILE_ALL:
945 return TAB_MOBILE;
946 case TEMPLATE_WIFI:
947 return TAB_WIFI;
948 default:
949 return null;
950 }
951 }
952
953 /**
Jeff Sharkey8e911d72011-06-14 22:41:21 -0700954 * Resolve best descriptive label for the given UID.
955 */
956 public static CharSequence resolveLabelForUid(PackageManager pm, int uid) {
957 final String[] packageNames = pm.getPackagesForUid(uid);
958 final int length = packageNames != null ? packageNames.length : 0;
959
960 CharSequence label = pm.getNameForUid(uid);
961 try {
962 if (length == 1) {
963 final ApplicationInfo info = pm.getApplicationInfo(packageNames[0], 0);
964 label = info.loadLabel(pm);
965 } else if (length > 1) {
966 for (String packageName : packageNames) {
967 final PackageInfo info = pm.getPackageInfo(packageName, 0);
968 if (info.sharedUserLabel != 0) {
969 label = pm.getText(packageName, info.sharedUserLabel, info.applicationInfo);
970 if (!TextUtils.isEmpty(label)) {
971 break;
972 }
973 }
974 }
975 }
976 } catch (NameNotFoundException e) {
977 }
978
979 if (TextUtils.isEmpty(label)) {
980 label = Integer.toString(uid);
981 }
982 return label;
983 }
984
Jeff Sharkeyab2d8d32011-05-30 16:19:56 -0700985}