1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.settings.datausage; 16 17 import static android.app.usage.NetworkStats.Bucket.UID_REMOVED; 18 import static android.app.usage.NetworkStats.Bucket.UID_TETHERING; 19 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; 20 21 import android.app.Activity; 22 import android.app.ActivityManager; 23 import android.app.settings.SettingsEnums; 24 import android.app.usage.NetworkStats; 25 import android.app.usage.NetworkStats.Bucket; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.pm.UserInfo; 29 import android.graphics.Color; 30 import android.net.ConnectivityManager; 31 import android.net.NetworkPolicy; 32 import android.net.NetworkTemplate; 33 import android.os.Bundle; 34 import android.os.Process; 35 import android.os.UserHandle; 36 import android.os.UserManager; 37 import android.provider.Settings; 38 import android.telephony.SubscriptionInfo; 39 import android.telephony.SubscriptionManager; 40 import android.util.EventLog; 41 import android.util.Log; 42 import android.util.SparseArray; 43 import android.view.View; 44 import android.view.View.AccessibilityDelegate; 45 import android.view.accessibility.AccessibilityEvent; 46 import android.widget.AdapterView; 47 import android.widget.AdapterView.OnItemSelectedListener; 48 import android.widget.ImageView; 49 import android.widget.Spinner; 50 51 import androidx.annotation.VisibleForTesting; 52 import androidx.lifecycle.Lifecycle; 53 import androidx.loader.app.LoaderManager.LoaderCallbacks; 54 import androidx.loader.content.Loader; 55 import androidx.preference.Preference; 56 import androidx.preference.PreferenceGroup; 57 58 import com.android.settings.R; 59 import com.android.settings.core.SubSettingLauncher; 60 import com.android.settings.datausage.CycleAdapter.SpinnerInterface; 61 import com.android.settings.network.MobileDataEnabledListener; 62 import com.android.settings.network.ProxySubscriptionManager; 63 import com.android.settings.widget.LoadingViewController; 64 import com.android.settingslib.AppItem; 65 import com.android.settingslib.net.NetworkCycleChartData; 66 import com.android.settingslib.net.NetworkCycleChartDataLoader; 67 import com.android.settingslib.net.NetworkStatsSummaryLoader; 68 import com.android.settingslib.net.UidDetailProvider; 69 70 import java.util.ArrayList; 71 import java.util.Collections; 72 import java.util.List; 73 import java.util.Objects; 74 75 /** 76 * Panel showing data usage history across various networks, including options 77 * to inspect based on usage cycle and control through {@link NetworkPolicy}. 78 */ 79 public class DataUsageList extends DataUsageBaseFragment 80 implements MobileDataEnabledListener.Client { 81 82 static final String EXTRA_SUB_ID = "sub_id"; 83 static final String EXTRA_NETWORK_TEMPLATE = "network_template"; 84 static final String EXTRA_NETWORK_TYPE = "network_type"; 85 86 private static final String TAG = "DataUsageList"; 87 private static final boolean LOGD = false; 88 89 private static final String KEY_USAGE_AMOUNT = "usage_amount"; 90 private static final String KEY_CHART_DATA = "chart_data"; 91 private static final String KEY_APPS_GROUP = "apps_group"; 92 private static final String KEY_TEMPLATE = "template"; 93 private static final String KEY_APP = "app"; 94 95 @VisibleForTesting 96 static final int LOADER_CHART_DATA = 2; 97 @VisibleForTesting 98 static final int LOADER_SUMMARY = 3; 99 100 @VisibleForTesting 101 MobileDataEnabledListener mDataStateListener; 102 103 @VisibleForTesting 104 NetworkTemplate mTemplate; 105 @VisibleForTesting 106 int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 107 @VisibleForTesting 108 int mNetworkType; 109 @VisibleForTesting 110 Spinner mCycleSpinner; 111 @VisibleForTesting 112 LoadingViewController mLoadingViewController; 113 114 private ChartDataUsagePreference mChart; 115 private List<NetworkCycleChartData> mCycleData; 116 // Caches the cycles for startAppDataUsage usage, which need be cleared when resumed. 117 private ArrayList<Long> mCycles; 118 // Spinner will keep the selected cycle even after paused, this only keeps the displayed cycle, 119 // which need be cleared when resumed. 120 private CycleAdapter.CycleItem mLastDisplayedCycle; 121 private UidDetailProvider mUidDetailProvider; 122 private CycleAdapter mCycleAdapter; 123 private Preference mUsageAmount; 124 private PreferenceGroup mApps; 125 private View mHeader; 126 127 @Override getMetricsCategory()128 public int getMetricsCategory() { 129 return SettingsEnums.DATA_USAGE_LIST; 130 } 131 132 @Override onCreate(Bundle savedInstanceState)133 public void onCreate(Bundle savedInstanceState) { 134 super.onCreate(savedInstanceState); 135 if (isGuestUser(getContext())) { 136 Log.e(TAG, "This setting isn't available for guest user"); 137 EventLog.writeEvent(0x534e4554, "262741858", -1 /* UID */, "Guest user"); 138 finish(); 139 return; 140 } 141 142 final Activity activity = getActivity(); 143 if (!isBandwidthControlEnabled()) { 144 Log.w(TAG, "No bandwidth control; leaving"); 145 activity.finish(); 146 return; 147 } 148 149 mUidDetailProvider = new UidDetailProvider(activity); 150 mUsageAmount = findPreference(KEY_USAGE_AMOUNT); 151 mChart = findPreference(KEY_CHART_DATA); 152 mApps = findPreference(KEY_APPS_GROUP); 153 154 final Preference unnecessaryWarningPreference = findPreference("operator_warning"); 155 if (unnecessaryWarningPreference != null) { 156 unnecessaryWarningPreference.setVisible(false); 157 } 158 159 processArgument(); 160 mDataStateListener = new MobileDataEnabledListener(activity, this); 161 } 162 163 @Override onViewCreated(View v, Bundle savedInstanceState)164 public void onViewCreated(View v, Bundle savedInstanceState) { 165 super.onViewCreated(v, savedInstanceState); 166 167 mHeader = setPinnedHeaderView(R.layout.apps_filter_spinner); 168 mHeader.findViewById(R.id.filter_settings).setOnClickListener(btn -> { 169 final Bundle args = new Bundle(); 170 args.putParcelable(DataUsageList.EXTRA_NETWORK_TEMPLATE, mTemplate); 171 new SubSettingLauncher(getContext()) 172 .setDestination(BillingCycleSettings.class.getName()) 173 .setTitleRes(R.string.billing_cycle) 174 .setSourceMetricsCategory(getMetricsCategory()) 175 .setArguments(args) 176 .launch(); 177 }); 178 mCycleSpinner = mHeader.findViewById(R.id.filter_spinner); 179 mCycleSpinner.setVisibility(View.GONE); 180 mCycleAdapter = new CycleAdapter(mCycleSpinner.getContext(), new SpinnerInterface() { 181 @Override 182 public void setAdapter(CycleAdapter cycleAdapter) { 183 mCycleSpinner.setAdapter(cycleAdapter); 184 } 185 186 @Override 187 public void setOnItemSelectedListener(OnItemSelectedListener listener) { 188 mCycleSpinner.setOnItemSelectedListener(listener); 189 } 190 191 @Override 192 public Object getSelectedItem() { 193 return mCycleSpinner.getSelectedItem(); 194 } 195 196 @Override 197 public void setSelection(int position) { 198 mCycleSpinner.setSelection(position); 199 } 200 }, mCycleListener); 201 mCycleSpinner.setAccessibilityDelegate(new AccessibilityDelegate() { 202 @Override 203 public void sendAccessibilityEvent(View host, int eventType) { 204 if (eventType == AccessibilityEvent.TYPE_VIEW_SELECTED) { 205 // Ignore TYPE_VIEW_SELECTED or TalkBack will speak for it at onResume. 206 return; 207 } 208 super.sendAccessibilityEvent(host, eventType); 209 } 210 }); 211 212 mLoadingViewController = new LoadingViewController( 213 getView().findViewById(R.id.loading_container), getListView()); 214 } 215 216 @Override onResume()217 public void onResume() { 218 super.onResume(); 219 mLoadingViewController.showLoadingViewDelayed(); 220 mDataStateListener.start(mSubId); 221 mCycles = null; 222 mLastDisplayedCycle = null; 223 224 // kick off loader for network history 225 // TODO: consider chaining two loaders together instead of reloading 226 // network history when showing app detail. 227 getLoaderManager().restartLoader(LOADER_CHART_DATA, 228 buildArgs(mTemplate), mNetworkCycleDataCallbacks); 229 230 updateBody(); 231 } 232 233 @Override onPause()234 public void onPause() { 235 super.onPause(); 236 mDataStateListener.stop(); 237 238 getLoaderManager().destroyLoader(LOADER_CHART_DATA); 239 getLoaderManager().destroyLoader(LOADER_SUMMARY); 240 } 241 242 @Override onDestroy()243 public void onDestroy() { 244 if (mUidDetailProvider != null) { 245 mUidDetailProvider.clearCache(); 246 mUidDetailProvider = null; 247 } 248 super.onDestroy(); 249 } 250 251 @Override getPreferenceScreenResId()252 protected int getPreferenceScreenResId() { 253 return R.xml.data_usage_list; 254 } 255 256 @Override getLogTag()257 protected String getLogTag() { 258 return TAG; 259 } 260 processArgument()261 void processArgument() { 262 final Bundle args = getArguments(); 263 if (args != null) { 264 mSubId = args.getInt(EXTRA_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID); 265 mTemplate = args.getParcelable(EXTRA_NETWORK_TEMPLATE); 266 mNetworkType = args.getInt(EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_MOBILE); 267 } 268 if (mTemplate == null && mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 269 final Intent intent = getIntent(); 270 mSubId = intent.getIntExtra(Settings.EXTRA_SUB_ID, 271 SubscriptionManager.INVALID_SUBSCRIPTION_ID); 272 mTemplate = intent.getParcelableExtra(Settings.EXTRA_NETWORK_TEMPLATE); 273 } 274 } 275 276 /** 277 * Implementation of {@code MobileDataEnabledListener.Client} 278 */ onMobileDataEnabledChange()279 public void onMobileDataEnabledChange() { 280 updatePolicy(); 281 } 282 283 /** 284 * Update body content based on current tab. Loads network cycle data from system, and 285 * binds them to visible controls. 286 */ updateBody()287 private void updateBody() { 288 if (!isAdded()) return; 289 290 final Context context = getActivity(); 291 292 // detail mode can change visible menus, invalidate 293 getActivity().invalidateOptionsMenu(); 294 295 int seriesColor = context.getColor(R.color.sim_noitification); 296 if (mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 297 final SubscriptionInfo sir = ProxySubscriptionManager.getInstance(context) 298 .getActiveSubscriptionInfo(mSubId); 299 300 if (sir != null) { 301 seriesColor = sir.getIconTint(); 302 } 303 } 304 305 final int secondaryColor = Color.argb(127, Color.red(seriesColor), Color.green(seriesColor), 306 Color.blue(seriesColor)); 307 mChart.setColors(seriesColor, secondaryColor); 308 } 309 buildArgs(NetworkTemplate template)310 private Bundle buildArgs(NetworkTemplate template) { 311 final Bundle args = new Bundle(); 312 args.putParcelable(KEY_TEMPLATE, template); 313 args.putParcelable(KEY_APP, null); 314 return args; 315 } 316 317 /** 318 * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for 319 * current {@link #mTemplate}. 320 */ 321 @VisibleForTesting updatePolicy()322 void updatePolicy() { 323 final NetworkPolicy policy = services.mPolicyEditor.getPolicy(mTemplate); 324 final View configureButton = mHeader.findViewById(R.id.filter_settings); 325 //SUB SELECT 326 if (isNetworkPolicyModifiable(policy, mSubId) && isMobileDataAvailable(mSubId)) { 327 mChart.setNetworkPolicy(policy); 328 configureButton.setVisibility(View.VISIBLE); 329 ((ImageView) configureButton).setColorFilter(android.R.color.white); 330 } else { 331 // controls are disabled; don't bind warning/limit sweeps 332 mChart.setNetworkPolicy(null); 333 configureButton.setVisibility(View.GONE); 334 } 335 336 // generate cycle list based on policy and available history 337 mCycleAdapter.updateCycleList(mCycleData); 338 updateSelectedCycle(); 339 } 340 341 /** 342 * Updates the chart and detail data when initial loaded or selected cycle changed. 343 */ updateSelectedCycle()344 private void updateSelectedCycle() { 345 // Avoid from updating UI after #onStop. 346 if (!getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { 347 return; 348 } 349 350 // Avoid from updating UI when async query still on-going. 351 // This could happen when a request from #onMobileDataEnabledChange. 352 if (mCycleData == null) { 353 return; 354 } 355 356 final int position = mCycleSpinner.getSelectedItemPosition(); 357 if (mCycleAdapter.getCount() == 0 || position < 0) { 358 return; 359 } 360 final CycleAdapter.CycleItem cycle = mCycleAdapter.getItem(position); 361 if (Objects.equals(cycle, mLastDisplayedCycle)) { 362 // Avoid duplicate update to avoid page flash. 363 return; 364 } 365 mLastDisplayedCycle = cycle; 366 367 if (LOGD) { 368 Log.d(TAG, "showing cycle " + cycle + ", [start=" + cycle.start + ", end=" 369 + cycle.end + "]"); 370 } 371 372 // update chart to show selected cycle, and update detail data 373 // to match updated sweep bounds. 374 mChart.setNetworkCycleData(mCycleData.get(position)); 375 376 updateDetailData(); 377 } 378 379 /** 380 * Update details based on {@link #mChart} inspection range depending on 381 * current mode. Updates {@link #mAdapter} with sorted list 382 * of applications data usage. 383 */ updateDetailData()384 private void updateDetailData() { 385 if (LOGD) Log.d(TAG, "updateDetailData()"); 386 387 // kick off loader for detailed stats 388 getLoaderManager().restartLoader(LOADER_SUMMARY, null /* args */, 389 mNetworkStatsDetailCallbacks); 390 391 final long totalBytes = mCycleData != null && !mCycleData.isEmpty() 392 ? mCycleData.get(mCycleSpinner.getSelectedItemPosition()).getTotalUsage() : 0; 393 final CharSequence totalPhrase = DataUsageUtils.formatDataUsage(getActivity(), totalBytes); 394 mUsageAmount.setTitle(getString(R.string.data_used_template, totalPhrase)); 395 } 396 397 /** 398 * Bind the given {@link NetworkStats}, or {@code null} to clear list. 399 */ bindStats(NetworkStats stats, int[] restrictedUids)400 private void bindStats(NetworkStats stats, int[] restrictedUids) { 401 mApps.removeAll(); 402 if (stats == null) { 403 if (LOGD) { 404 Log.d(TAG, "No network stats data. App list cleared."); 405 } 406 return; 407 } 408 409 final ArrayList<AppItem> items = new ArrayList<>(); 410 long largest = 0; 411 412 final int currentUserId = ActivityManager.getCurrentUser(); 413 final UserManager userManager = UserManager.get(getContext()); 414 final List<UserHandle> profiles = userManager.getUserProfiles(); 415 final SparseArray<AppItem> knownItems = new SparseArray<AppItem>(); 416 417 final Bucket bucket = new Bucket(); 418 while (stats.hasNextBucket() && stats.getNextBucket(bucket)) { 419 // Decide how to collapse items together 420 final int uid = bucket.getUid(); 421 final int collapseKey; 422 final int category; 423 final int userId = UserHandle.getUserId(uid); 424 if (UserHandle.isApp(uid) || Process.isSdkSandboxUid(uid)) { 425 if (profiles.contains(new UserHandle(userId))) { 426 if (userId != currentUserId) { 427 // Add to a managed user item. 428 final int managedKey = UidDetailProvider.buildKeyForUser(userId); 429 largest = accumulate(managedKey, knownItems, bucket, 430 AppItem.CATEGORY_USER, items, largest); 431 } 432 // Map SDK sandbox back to its corresponding app 433 if (Process.isSdkSandboxUid(uid)) { 434 collapseKey = Process.getAppUidForSdkSandboxUid(uid); 435 } else { 436 collapseKey = uid; 437 } 438 category = AppItem.CATEGORY_APP; 439 } else { 440 // If it is a removed user add it to the removed users' key 441 final UserInfo info = userManager.getUserInfo(userId); 442 if (info == null) { 443 collapseKey = UID_REMOVED; 444 category = AppItem.CATEGORY_APP; 445 } else { 446 // Add to other user item. 447 collapseKey = UidDetailProvider.buildKeyForUser(userId); 448 category = AppItem.CATEGORY_USER; 449 } 450 } 451 } else if (uid == UID_REMOVED || uid == UID_TETHERING 452 || uid == Process.OTA_UPDATE_UID) { 453 collapseKey = uid; 454 category = AppItem.CATEGORY_APP; 455 } else { 456 collapseKey = android.os.Process.SYSTEM_UID; 457 category = AppItem.CATEGORY_APP; 458 } 459 largest = accumulate(collapseKey, knownItems, bucket, category, items, largest); 460 } 461 stats.close(); 462 463 final int restrictedUidsMax = restrictedUids.length; 464 for (int i = 0; i < restrictedUidsMax; ++i) { 465 final int uid = restrictedUids[i]; 466 // Only splice in restricted state for current user or managed users 467 if (!profiles.contains(new UserHandle(UserHandle.getUserId(uid)))) { 468 continue; 469 } 470 471 AppItem item = knownItems.get(uid); 472 if (item == null) { 473 item = new AppItem(uid); 474 item.total = -1; 475 item.addUid(uid); 476 items.add(item); 477 knownItems.put(item.key, item); 478 } 479 item.restricted = true; 480 } 481 482 Collections.sort(items); 483 for (int i = 0; i < items.size(); i++) { 484 final int percentTotal = largest != 0 ? (int) (items.get(i).total * 100 / largest) : 0; 485 final AppDataUsagePreference preference = new AppDataUsagePreference(getContext(), 486 items.get(i), percentTotal, mUidDetailProvider); 487 preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { 488 @Override 489 public boolean onPreferenceClick(Preference preference) { 490 AppDataUsagePreference pref = (AppDataUsagePreference) preference; 491 AppItem item = pref.getItem(); 492 startAppDataUsage(item); 493 return true; 494 } 495 }); 496 mApps.addPreference(preference); 497 } 498 } 499 500 @VisibleForTesting startAppDataUsage(AppItem item)501 void startAppDataUsage(AppItem item) { 502 final Bundle args = new Bundle(); 503 args.putParcelable(AppDataUsage.ARG_APP_ITEM, item); 504 args.putParcelable(AppDataUsage.ARG_NETWORK_TEMPLATE, mTemplate); 505 if (mCycles == null) { 506 mCycles = new ArrayList<>(); 507 for (NetworkCycleChartData data : mCycleData) { 508 if (mCycles.isEmpty()) { 509 mCycles.add(data.getEndTime()); 510 } 511 mCycles.add(data.getStartTime()); 512 } 513 } 514 args.putSerializable(AppDataUsage.ARG_NETWORK_CYCLES, mCycles); 515 args.putLong(AppDataUsage.ARG_SELECTED_CYCLE, 516 mCycleData.get(mCycleSpinner.getSelectedItemPosition()).getEndTime()); 517 518 new SubSettingLauncher(getContext()) 519 .setDestination(AppDataUsage.class.getName()) 520 .setTitleRes(R.string.data_usage_app_summary_title) 521 .setArguments(args) 522 .setSourceMetricsCategory(getMetricsCategory()) 523 .launch(); 524 } 525 526 /** 527 * Accumulate data usage of a network stats entry for the item mapped by the collapse key. 528 * Creates the item if needed. 529 * 530 * @param collapseKey the collapse key used to map the item. 531 * @param knownItems collection of known (already existing) items. 532 * @param bucket the network stats bucket to extract data usage from. 533 * @param itemCategory the item is categorized on the list view by this category. Must be 534 */ accumulate(int collapseKey, final SparseArray<AppItem> knownItems, Bucket bucket, int itemCategory, ArrayList<AppItem> items, long largest)535 private static long accumulate(int collapseKey, final SparseArray<AppItem> knownItems, 536 Bucket bucket, int itemCategory, ArrayList<AppItem> items, long largest) { 537 final int uid = bucket.getUid(); 538 AppItem item = knownItems.get(collapseKey); 539 if (item == null) { 540 item = new AppItem(collapseKey); 541 item.category = itemCategory; 542 items.add(item); 543 knownItems.put(item.key, item); 544 } 545 item.addUid(uid); 546 item.total += bucket.getRxBytes() + bucket.getTxBytes(); 547 return Math.max(largest, item.total); 548 } 549 550 private final OnItemSelectedListener mCycleListener = new OnItemSelectedListener() { 551 @Override 552 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 553 updateSelectedCycle(); 554 } 555 556 @Override 557 public void onNothingSelected(AdapterView<?> parent) { 558 // ignored 559 } 560 }; 561 562 @VisibleForTesting 563 final LoaderCallbacks<List<NetworkCycleChartData>> mNetworkCycleDataCallbacks = 564 new LoaderCallbacks<List<NetworkCycleChartData>>() { 565 @Override 566 public Loader<List<NetworkCycleChartData>> onCreateLoader(int id, Bundle args) { 567 return NetworkCycleChartDataLoader.builder(getContext()) 568 .setNetworkTemplate(mTemplate) 569 .build(); 570 } 571 572 @Override 573 public void onLoadFinished(Loader<List<NetworkCycleChartData>> loader, 574 List<NetworkCycleChartData> data) { 575 mLoadingViewController.showContent(false /* animate */); 576 mCycleData = data; 577 // calculate policy cycles based on available data 578 updatePolicy(); 579 mCycleSpinner.setVisibility(View.VISIBLE); 580 } 581 582 @Override 583 public void onLoaderReset(Loader<List<NetworkCycleChartData>> loader) { 584 mCycleData = null; 585 } 586 }; 587 588 private final LoaderCallbacks<NetworkStats> mNetworkStatsDetailCallbacks = 589 new LoaderCallbacks<NetworkStats>() { 590 @Override 591 public Loader<NetworkStats> onCreateLoader(int id, Bundle args) { 592 return new NetworkStatsSummaryLoader.Builder(getContext()) 593 .setStartTime(mChart.getInspectStart()) 594 .setEndTime(mChart.getInspectEnd()) 595 .setNetworkTemplate(mTemplate) 596 .build(); 597 } 598 599 @Override 600 public void onLoadFinished(Loader<NetworkStats> loader, NetworkStats data) { 601 final int[] restrictedUids = services.mPolicyManager.getUidsWithPolicy( 602 POLICY_REJECT_METERED_BACKGROUND); 603 bindStats(data, restrictedUids); 604 updateEmptyVisible(); 605 } 606 607 @Override 608 public void onLoaderReset(Loader<NetworkStats> loader) { 609 bindStats(null, new int[0]); 610 updateEmptyVisible(); 611 } 612 613 private void updateEmptyVisible() { 614 if ((mApps.getPreferenceCount() != 0) != 615 (getPreferenceScreen().getPreferenceCount() != 0)) { 616 if (mApps.getPreferenceCount() != 0) { 617 getPreferenceScreen().addPreference(mUsageAmount); 618 getPreferenceScreen().addPreference(mApps); 619 } else { 620 getPreferenceScreen().removeAll(); 621 } 622 } 623 } 624 }; 625 isGuestUser(Context context)626 private static boolean isGuestUser(Context context) { 627 if (context == null) return false; 628 final UserManager userManager = context.getSystemService(UserManager.class); 629 if (userManager == null) return false; 630 return userManager.isGuestUser(); 631 } 632 } 633