1 /* 2 * Copyright (C) 2021 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 17 package com.android.permissioncontroller.permission.ui.handheld.dashboard; 18 19 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID; 20 import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID; 21 22 import static java.util.concurrent.TimeUnit.DAYS; 23 import static java.util.concurrent.TimeUnit.HOURS; 24 import static java.util.concurrent.TimeUnit.MINUTES; 25 26 import android.Manifest.permission_group; 27 import android.app.ActionBar; 28 import android.app.Activity; 29 import android.app.AppOpsManager.OpEventProxyInfo; 30 import android.app.role.RoleManager; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.res.ColorStateList; 34 import android.content.res.Configuration; 35 import android.content.res.TypedArray; 36 import android.os.Build; 37 import android.os.Bundle; 38 import android.os.UserHandle; 39 import android.text.format.DateFormat; 40 import android.util.ArraySet; 41 import android.view.LayoutInflater; 42 import android.view.Menu; 43 import android.view.MenuInflater; 44 import android.view.MenuItem; 45 import android.view.View; 46 import android.view.ViewGroup; 47 48 import androidx.annotation.NonNull; 49 import androidx.annotation.Nullable; 50 import androidx.annotation.RequiresApi; 51 import androidx.coordinatorlayout.widget.CoordinatorLayout; 52 import androidx.preference.Preference; 53 import androidx.preference.PreferenceCategory; 54 import androidx.preference.PreferenceScreen; 55 import androidx.recyclerview.widget.RecyclerView; 56 57 import com.android.permissioncontroller.PermissionControllerApplication; 58 import com.android.permissioncontroller.R; 59 import com.android.permissioncontroller.permission.model.AppPermissionGroup; 60 import com.android.permissioncontroller.permission.model.AppPermissionUsage; 61 import com.android.permissioncontroller.permission.model.PermissionUsages; 62 import com.android.permissioncontroller.permission.model.legacy.PermissionApps; 63 import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity; 64 import com.android.permissioncontroller.permission.ui.handheld.SettingsWithLargeHeader; 65 import com.android.permissioncontroller.permission.utils.KotlinUtils; 66 import com.android.permissioncontroller.permission.utils.Utils; 67 68 import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton; 69 70 import java.time.ZonedDateTime; 71 import java.time.temporal.ChronoUnit; 72 import java.util.ArrayList; 73 import java.util.Arrays; 74 import java.util.Collection; 75 import java.util.Collections; 76 import java.util.List; 77 import java.util.Objects; 78 import java.util.Set; 79 import java.util.concurrent.atomic.AtomicBoolean; 80 import java.util.concurrent.atomic.AtomicReference; 81 import java.util.stream.Collectors; 82 import java.util.stream.Stream; 83 84 import kotlin.Triple; 85 86 /** 87 * The permission details page showing the history/timeline of a permission 88 */ 89 @RequiresApi(Build.VERSION_CODES.S) 90 public class PermissionDetailsFragment extends SettingsWithLargeHeader implements 91 PermissionUsages.PermissionsUsagesChangeCallback { 92 public static final int FILTER_24_HOURS = 2; 93 94 private static final List<String> ALLOW_CLUSTERING_PERMISSION_GROUPS = Arrays.asList( 95 permission_group.LOCATION, permission_group.CAMERA, permission_group.MICROPHONE 96 ); 97 private static final int ONE_HOUR_MS = 3600000; 98 private static final int ONE_MINUTE_MS = 60000; 99 private static final int CLUSTER_MINUTES_APART = 1; 100 101 private static final String KEY_SHOW_SYSTEM_PREFS = "_show_system"; 102 private static final String SHOW_SYSTEM_KEY = PermissionDetailsFragment.class.getName() 103 + KEY_SHOW_SYSTEM_PREFS; 104 105 private static final String KEY_SESSION_ID = "_session_id"; 106 private static final String SESSION_ID_KEY = PermissionDetailsFragment.class.getName() 107 + KEY_SESSION_ID; 108 109 private @Nullable String mFilterGroup; 110 private @Nullable List<AppPermissionUsage> mAppPermissionUsages = new ArrayList<>(); 111 private @NonNull List<TimeFilterItem> mFilterTimes; 112 private int mFilterTimeIndex; 113 private @NonNull PermissionUsages mPermissionUsages; 114 private boolean mFinishedInitialLoad; 115 116 private boolean mShowSystem; 117 private boolean mHasSystemApps; 118 119 private MenuItem mShowSystemMenu; 120 private MenuItem mHideSystemMenu; 121 private @NonNull RoleManager mRoleManager; 122 123 private long mSessionId; 124 125 @Override onCreate(Bundle savedInstanceState)126 public void onCreate(Bundle savedInstanceState) { 127 super.onCreate(savedInstanceState); 128 129 mFinishedInitialLoad = false; 130 initializeTimeFilter(); 131 mFilterTimeIndex = FILTER_24_HOURS; 132 133 if (savedInstanceState != null) { 134 mShowSystem = savedInstanceState.getBoolean(SHOW_SYSTEM_KEY); 135 mSessionId = savedInstanceState.getLong(SESSION_ID_KEY); 136 } else { 137 mShowSystem = getArguments().getBoolean( 138 ManagePermissionsActivity.EXTRA_SHOW_SYSTEM, false); 139 mSessionId = getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID); 140 } 141 142 if (mFilterGroup == null) { 143 mFilterGroup = getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME); 144 } 145 146 setHasOptionsMenu(true); 147 ActionBar ab = getActivity().getActionBar(); 148 if (ab != null) { 149 ab.setDisplayHomeAsUpEnabled(true); 150 } 151 152 Context context = getPreferenceManager().getContext(); 153 154 mPermissionUsages = new PermissionUsages(context); 155 mRoleManager = Utils.getSystemServiceSafe(context, RoleManager.class); 156 157 reloadData(); 158 } 159 160 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)161 public View onCreateView(LayoutInflater inflater, ViewGroup container, 162 Bundle savedInstanceState) { 163 ViewGroup rootView = (ViewGroup) super.onCreateView(inflater, container, 164 savedInstanceState); 165 166 PermissionDetailsWrapperFragment parentFragment = (PermissionDetailsWrapperFragment) 167 requireParentFragment(); 168 CoordinatorLayout coordinatorLayout = parentFragment.getCoordinatorLayout(); 169 inflater.inflate(R.layout.permission_details_extended_fab, coordinatorLayout); 170 ExtendedFloatingActionButton extendedFab = coordinatorLayout.requireViewById( 171 R.id.extended_fab); 172 // Load the background tint color from the application theme 173 // rather than the Material Design theme 174 Activity activity = getActivity(); 175 ColorStateList backgroundColor = activity.getColorStateList( 176 android.R.color.system_accent3_100); 177 extendedFab.setBackgroundTintList(backgroundColor); 178 extendedFab.setText(R.string.manage_permission); 179 boolean isUiModeNight = (activity.getResources().getConfiguration().uiMode 180 & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; 181 int textColorAttr = isUiModeNight ? android.R.attr.textColorPrimaryInverse 182 : android.R.attr.textColorPrimary; 183 TypedArray typedArray = activity.obtainStyledAttributes(new int[] { textColorAttr }); 184 ColorStateList textColor = typedArray.getColorStateList(0); 185 typedArray.recycle(); 186 extendedFab.setTextColor(textColor); 187 extendedFab.setIcon(activity.getDrawable(R.drawable.ic_settings_outline)); 188 extendedFab.setVisibility(View.VISIBLE); 189 extendedFab.setOnClickListener(view -> { 190 Intent intent = new Intent(Intent.ACTION_MANAGE_PERMISSION_APPS) 191 .putExtra(Intent.EXTRA_PERMISSION_NAME, mFilterGroup); 192 startActivity(intent); 193 }); 194 RecyclerView recyclerView = getListView(); 195 int bottomPadding = getResources() 196 .getDimensionPixelSize(R.dimen.privhub_details_recycler_view_bottom_padding); 197 recyclerView.setPadding(0, 0, 0, bottomPadding); 198 recyclerView.setClipToPadding(false); 199 recyclerView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY); 200 201 return rootView; 202 } 203 204 @Override onStart()205 public void onStart() { 206 super.onStart(); 207 CharSequence title = getString(R.string.permission_history_title); 208 if (mFilterGroup != null) { 209 title = getResources().getString(R.string.permission_group_usage_title, 210 KotlinUtils.INSTANCE.getPermGroupLabel(getActivity(), mFilterGroup)); 211 } 212 getActivity().setTitle(title); 213 } 214 215 @Override onPermissionUsagesChanged()216 public void onPermissionUsagesChanged() { 217 if (mPermissionUsages.getUsages().isEmpty()) { 218 return; 219 } 220 mAppPermissionUsages = new ArrayList<>(mPermissionUsages.getUsages()); 221 222 // Ensure the group name is valid. 223 if (getGroup(mFilterGroup) == null) { 224 mFilterGroup = null; 225 } 226 227 updateUI(); 228 } 229 230 @Override onSaveInstanceState(Bundle outState)231 public void onSaveInstanceState(Bundle outState) { 232 super.onSaveInstanceState(outState); 233 outState.putBoolean(SHOW_SYSTEM_KEY, mShowSystem); 234 outState.putLong(SESSION_ID_KEY, mSessionId); 235 } 236 237 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)238 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 239 mShowSystemMenu = menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE, 240 R.string.menu_show_system); 241 mHideSystemMenu = menu.add(Menu.NONE, MENU_HIDE_SYSTEM, Menu.NONE, 242 R.string.menu_hide_system); 243 244 updateMenu(); 245 } 246 updateMenu()247 private void updateMenu() { 248 if (mHasSystemApps) { 249 mShowSystemMenu.setVisible(!mShowSystem); 250 mShowSystemMenu.setEnabled(true); 251 252 mHideSystemMenu.setVisible(mShowSystem); 253 mHideSystemMenu.setEnabled(true); 254 } else { 255 mShowSystemMenu.setVisible(true); 256 mShowSystemMenu.setEnabled(false); 257 258 mHideSystemMenu.setVisible(false); 259 mHideSystemMenu.setEnabled(false); 260 } 261 } 262 263 @Override onOptionsItemSelected(MenuItem item)264 public boolean onOptionsItemSelected(MenuItem item) { 265 switch (item.getItemId()) { 266 case android.R.id.home: 267 getActivity().finishAfterTransition(); 268 return true; 269 case MENU_SHOW_SYSTEM: 270 case MENU_HIDE_SYSTEM: 271 mShowSystem = item.getItemId() == MENU_SHOW_SYSTEM; 272 // We already loaded all data, so don't reload 273 updateUI(); 274 updateMenu(); 275 break; 276 } 277 278 return super.onOptionsItemSelected(item); 279 } 280 updateUI()281 private void updateUI() { 282 if (mAppPermissionUsages.isEmpty() || getActivity() == null) { 283 return; 284 } 285 Context context = getActivity(); 286 PreferenceScreen screen = getPreferenceScreen(); 287 if (screen == null) { 288 screen = getPreferenceManager().createPreferenceScreen(context); 289 setPreferenceScreen(screen); 290 } 291 screen.removeAll(); 292 293 final TimeFilterItem timeFilterItem = mFilterTimes.get(mFilterTimeIndex); 294 long curTime = System.currentTimeMillis(); 295 long startTime = Math.max(timeFilterItem == null ? 0 : (curTime - timeFilterItem.getTime()), 296 0); 297 298 Set<String> exemptedPackages = Utils.getExemptedPackages(mRoleManager); 299 300 Preference subtitlePreference = new Preference(context); 301 subtitlePreference.setSummary( 302 getResources().getString(R.string.permission_group_usage_subtitle, 303 KotlinUtils.INSTANCE.getPermGroupLabel(getActivity(), mFilterGroup))); 304 subtitlePreference.setSelectable(false); 305 screen.addPreference(subtitlePreference); 306 307 AtomicBoolean seenSystemApp = new AtomicBoolean(false); 308 309 ArrayList<PermissionApps.PermissionApp> permApps = new ArrayList<>(); 310 List<AppPermissionUsageEntry> usages = mAppPermissionUsages.stream() 311 .filter(appUsage -> !exemptedPackages.contains(appUsage.getPackageName())) 312 .map(appUsage -> { 313 // Fetch the access time list of the app accesses mFilterGroup permission group 314 // The DiscreteAccessTime is a Triple of (access time, access duration, proxy) of that 315 // app 316 List<Triple<Long, Long, OpEventProxyInfo>> discreteAccessTimeList = 317 new ArrayList<>(); 318 List<AppPermissionUsage.GroupUsage> appGroups = appUsage.getGroupUsages(); 319 int numGroups = appGroups.size(); 320 for (int groupIndex = 0; groupIndex < numGroups; groupIndex++) { 321 AppPermissionUsage.GroupUsage groupUsage = appGroups.get(groupIndex); 322 if (!groupUsage.getGroup().getName().equals(mFilterGroup) 323 || !groupUsage.hasDiscreteData()) { 324 continue; 325 } 326 327 final boolean isSystemApp = !Utils.isGroupOrBgGroupUserSensitive( 328 groupUsage.getGroup()); 329 seenSystemApp.set(seenSystemApp.get() || isSystemApp); 330 if (isSystemApp && !mShowSystem) { 331 continue; 332 } 333 334 List<Triple<Long, Long, OpEventProxyInfo>> allDiscreteAccessTime = 335 groupUsage.getAllDiscreteAccessTime(); 336 int numAllDiscreteAccessTime = allDiscreteAccessTime.size(); 337 for (int discreteAccessTimeIndex = 0; 338 discreteAccessTimeIndex < numAllDiscreteAccessTime; 339 discreteAccessTimeIndex++) { 340 Triple<Long, Long, OpEventProxyInfo> discreteAccessTime = 341 allDiscreteAccessTime.get(discreteAccessTimeIndex); 342 if (discreteAccessTime.getFirst() == 0 343 || discreteAccessTime.getFirst() < startTime) { 344 continue; 345 } 346 347 discreteAccessTimeList.add(discreteAccessTime); 348 } 349 } 350 351 Collections.sort( 352 discreteAccessTimeList, (x, y) -> y.getFirst().compareTo(x.getFirst())); 353 354 if (discreteAccessTimeList.size() > 0) { 355 permApps.add(appUsage.getApp()); 356 } 357 358 // If the current permission group is not LOCATION or there's only one access 359 // for the app, return individual entry early. 360 if (!ALLOW_CLUSTERING_PERMISSION_GROUPS.contains(mFilterGroup) 361 || discreteAccessTimeList.size() <= 1) { 362 return discreteAccessTimeList.stream().map( 363 time -> new AppPermissionUsageEntry(appUsage, time.getFirst(), 364 Collections.singletonList(time))) 365 .collect(Collectors.toList()); 366 } 367 368 // Group access time list 369 List<AppPermissionUsageEntry> usageEntries = new ArrayList<>(); 370 AppPermissionUsageEntry ongoingEntry = null; 371 for (Triple<Long, Long, OpEventProxyInfo> time : discreteAccessTimeList) { 372 if (ongoingEntry == null) { 373 ongoingEntry = new AppPermissionUsageEntry(appUsage, time.getFirst(), 374 Stream.of(time) 375 .collect(Collectors.toCollection(ArrayList::new))); 376 } else { 377 List<Triple<Long, Long, OpEventProxyInfo>> ongoingAccessTimeList = 378 ongoingEntry.mClusteredAccessTimeList; 379 if (time.getFirst() / ONE_HOUR_MS 380 != ongoingAccessTimeList.get(0).getFirst() / ONE_HOUR_MS 381 || ongoingAccessTimeList.get(ongoingAccessTimeList.size() - 1) 382 .getFirst() 383 / ONE_MINUTE_MS - time.getFirst() / ONE_MINUTE_MS 384 > CLUSTER_MINUTES_APART) { 385 // If the current access time is not in the same hour nor within 386 // CLUSTER_MINUTES_APART, add the ongoing entry to the usage list 387 // and start a new ongoing entry. 388 usageEntries.add(ongoingEntry); 389 ongoingEntry = new AppPermissionUsageEntry(appUsage, 390 time.getFirst(), Stream.of(time) 391 .collect(Collectors.toCollection(ArrayList::new))); 392 } else { 393 ongoingAccessTimeList.add(time); 394 } 395 } 396 } 397 usageEntries.add(ongoingEntry); 398 399 return usageEntries; 400 }).flatMap(Collection::stream).sorted((x, y) -> { 401 // Sort all usage entries by startTime desc, and then by app name. 402 int timeCompare = Long.compare(y.mEndTime, x.mEndTime); 403 if (timeCompare != 0) { 404 return timeCompare; 405 } 406 return x.mAppPermissionUsage.getApp().getLabel().compareTo( 407 y.mAppPermissionUsage.getApp().getLabel()); 408 }).collect(Collectors.toList()); 409 410 if (mHasSystemApps != seenSystemApp.get()) { 411 mHasSystemApps = seenSystemApp.get(); 412 getActivity().invalidateOptionsMenu(); 413 } 414 415 // Truncate to midnight in current timezone. 416 final long midnightToday = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).toEpochSecond() 417 * 1000L; 418 AppPermissionUsageEntry midnightTodayEntry = new AppPermissionUsageEntry( 419 null, midnightToday, null); 420 421 // Use the placeholder pair midnightTodayPair to get 422 // the index of the first usage entry from yesterday 423 int todayCategoryIndex = 0; 424 int yesterdayCategoryIndex = Collections.binarySearch(usages, 425 midnightTodayEntry, (e1, e2) -> Long.compare(e2.getEndTime(), e1.getEndTime())); 426 if (yesterdayCategoryIndex < 0) { 427 yesterdayCategoryIndex = -1 * (yesterdayCategoryIndex + 1); 428 } 429 430 // Make these variables effectively final so that 431 // we can use these captured variables in the below lambda expression 432 AtomicReference<PreferenceCategory> category = new AtomicReference<>( 433 createDayCategoryPreference(context)); 434 screen.addPreference(category.get()); 435 PreferenceScreen finalScreen = screen; 436 int finalYesterdayCategoryIndex = yesterdayCategoryIndex; 437 438 new PermissionApps.AppDataLoader(context, () -> { 439 final int numUsages = usages.size(); 440 for (int usageNum = 0; usageNum < numUsages; usageNum++) { 441 AppPermissionUsageEntry usage = usages.get(usageNum); 442 if (finalYesterdayCategoryIndex == usageNum) { 443 if (finalYesterdayCategoryIndex != todayCategoryIndex) { 444 // We create a new category only when we need it. 445 // We will not create a new category if we only need one category for 446 // either today's or yesterday's usage 447 category.set(createDayCategoryPreference(context)); 448 finalScreen.addPreference(category.get()); 449 } 450 category.get().setTitle(R.string.permission_history_category_yesterday); 451 } else if (todayCategoryIndex == usageNum) { 452 category.get().setTitle(R.string.permission_history_category_today); 453 } 454 455 String accessTime = DateFormat.getTimeFormat(context).format(usage.mEndTime); 456 Long durationLong = usage.mClusteredAccessTimeList 457 .stream() 458 .map(p -> p.getSecond()) 459 .filter(dur -> dur > 0) 460 .reduce(0L, (dur1, dur2) -> dur1 + dur2); 461 462 List<Long> accessTimeList = usage.mClusteredAccessTimeList 463 .stream().map(p -> p.getFirst()).collect(Collectors.toList()); 464 ArrayList<String> attributionTags = 465 usage.mAppPermissionUsage.getGroupUsages().stream().filter(groupUsage -> 466 groupUsage.getGroup().getName().equals(mFilterGroup)).map( 467 AppPermissionUsage.GroupUsage::getAttributionTags).filter( 468 Objects::nonNull).flatMap(Collection::stream).collect( 469 Collectors.toCollection(ArrayList::new)); 470 471 // Determine the preference summary. Start with the duration string 472 String summaryLabel = null; 473 // Since Location accesses are atomic, we manually calculate the access duration 474 // by comparing the first and last access within the cluster 475 if (mFilterGroup.equals(permission_group.LOCATION)) { 476 if (accessTimeList.size() > 1) { 477 durationLong = accessTimeList.get(0) 478 - accessTimeList.get(accessTimeList.size() - 1); 479 480 // Similar to other history items, only show the duration if it's longer 481 // than the clustering granularity. 482 if (durationLong 483 >= (MINUTES.toMillis(CLUSTER_MINUTES_APART) + 1)) { 484 summaryLabel = UtilsKt.getDurationUsedStr(context, durationLong); 485 } 486 } 487 } else { 488 // Only show the duration if it is at least (cluster + 1) minutes. Displaying 489 // times that are the same as the cluster granularity does not convey useful 490 // information. 491 if ((durationLong != null) 492 && durationLong >= MINUTES.toMillis(CLUSTER_MINUTES_APART + 1)) { 493 summaryLabel = UtilsKt.getDurationUsedStr(context, durationLong); 494 } 495 } 496 497 String proxyPackageLabel = null; 498 for (int i = 0; i < usage.mClusteredAccessTimeList.size(); i++) { 499 OpEventProxyInfo proxy = usage.mClusteredAccessTimeList.get(i).getThird(); 500 if (proxy != null && proxy.getPackageName() != null) { 501 proxyPackageLabel = KotlinUtils.INSTANCE.getPackageLabel( 502 PermissionControllerApplication.get(), proxy.getPackageName(), 503 UserHandle.getUserHandleForUid(proxy.getUid())); 504 break; 505 } 506 } 507 508 // if we have both a proxy and a duration, combine the two. 509 if (summaryLabel != null && proxyPackageLabel != null) { 510 summaryLabel = context.getString(R.string.permission_usage_duration_and_proxy, 511 proxyPackageLabel, summaryLabel); 512 } else { 513 summaryLabel = proxyPackageLabel; 514 } 515 516 PermissionHistoryPreference permissionUsagePreference = new 517 PermissionHistoryPreference(context, 518 UserHandle.getUserHandleForUid(usage.mAppPermissionUsage.getApp().getUid()), 519 usage.mAppPermissionUsage.getPackageName(), 520 usage.mAppPermissionUsage.getApp().getIcon(), 521 usage.mAppPermissionUsage.getApp().getLabel(), 522 mFilterGroup, accessTime, summaryLabel, accessTimeList, attributionTags, 523 usageNum == (numUsages - 1), 524 mSessionId 525 ); 526 527 category.get().addPreference(permissionUsagePreference); 528 } 529 530 setLoading(false, true); 531 mFinishedInitialLoad = true; 532 setProgressBarVisible(false); 533 mPermissionUsages.stopLoader(getActivity().getLoaderManager()); 534 535 }).execute(permApps.toArray(new PermissionApps.PermissionApp[permApps.size()])); 536 } 537 createDayCategoryPreference(Context context)538 private PreferenceCategory createDayCategoryPreference(Context context) { 539 PreferenceCategory category = new PreferenceCategory(context); 540 // Do not reserve icon space, so that the text moves all the way left. 541 category.setIconSpaceReserved(false); 542 return category; 543 } 544 545 /** 546 * Get an AppPermissionGroup that represents the given permission group (and an arbitrary app). 547 * 548 * @param groupName The name of the permission group. 549 * 550 * @return an AppPermissionGroup representing the given permission group or null if no such 551 * AppPermissionGroup is found. 552 */ getGroup(@onNull String groupName)553 private @Nullable AppPermissionGroup getGroup(@NonNull String groupName) { 554 List<AppPermissionGroup> groups = getOSPermissionGroups(); 555 int numGroups = groups.size(); 556 for (int i = 0; i < numGroups; i++) { 557 if (groups.get(i).getName().equals(groupName)) { 558 return groups.get(i); 559 } 560 } 561 return null; 562 } 563 564 /** 565 * Get the permission groups declared by the OS. 566 * 567 * TODO: theianchen change the method name to make that clear, 568 * and return a list of string group names, not AppPermissionGroups. 569 * @return a list of the permission groups declared by the OS. 570 */ getOSPermissionGroups()571 private @NonNull List<AppPermissionGroup> getOSPermissionGroups() { 572 final List<AppPermissionGroup> groups = new ArrayList<>(); 573 final Set<String> seenGroups = new ArraySet<>(); 574 final int numGroups = mAppPermissionUsages.size(); 575 for (int i = 0; i < numGroups; i++) { 576 final AppPermissionUsage appUsage = mAppPermissionUsages.get(i); 577 final List<AppPermissionUsage.GroupUsage> groupUsages = appUsage.getGroupUsages(); 578 final int groupUsageCount = groupUsages.size(); 579 for (int j = 0; j < groupUsageCount; j++) { 580 final AppPermissionUsage.GroupUsage groupUsage = groupUsages.get(j); 581 if (Utils.isModernPermissionGroup(groupUsage.getGroup().getName())) { 582 if (seenGroups.add(groupUsage.getGroup().getName())) { 583 groups.add(groupUsage.getGroup()); 584 } 585 } 586 } 587 } 588 return groups; 589 } 590 reloadData()591 private void reloadData() { 592 final TimeFilterItem timeFilterItem = mFilterTimes.get(mFilterTimeIndex); 593 final long filterTimeBeginMillis = Math.max(System.currentTimeMillis() 594 - timeFilterItem.getTime(), 0); 595 mPermissionUsages.load(null /*filterPackageName*/, null /*filterPermissionGroups*/, 596 filterTimeBeginMillis, Long.MAX_VALUE, PermissionUsages.USAGE_FLAG_LAST 597 | PermissionUsages.USAGE_FLAG_HISTORICAL, getActivity().getLoaderManager(), 598 false /*getUiInfo*/, false /*getNonPlatformPermissions*/, this /*callback*/, 599 false /*sync*/); 600 if (mFinishedInitialLoad) { 601 setProgressBarVisible(true); 602 } 603 } 604 605 /** 606 * Initialize the time filter to show the smallest entry greater than the time passed in as an 607 * argument. If nothing is passed, this simply initializes the possible values. 608 */ initializeTimeFilter()609 private void initializeTimeFilter() { 610 Context context = getPreferenceManager().getContext(); 611 mFilterTimes = new ArrayList<>(); 612 mFilterTimes.add(new TimeFilterItem(Long.MAX_VALUE, 613 context.getString(R.string.permission_usage_any_time))); 614 mFilterTimes.add(new TimeFilterItem(DAYS.toMillis(7), 615 context.getString(R.string.permission_usage_last_7_days))); 616 mFilterTimes.add(new TimeFilterItem(DAYS.toMillis(1), 617 context.getString(R.string.permission_usage_last_day))); 618 mFilterTimes.add(new TimeFilterItem(HOURS.toMillis(1), 619 context.getString(R.string.permission_usage_last_hour))); 620 mFilterTimes.add(new TimeFilterItem(MINUTES.toMillis(15), 621 context.getString(R.string.permission_usage_last_15_minutes))); 622 mFilterTimes.add(new TimeFilterItem(MINUTES.toMillis(1), 623 context.getString(R.string.permission_usage_last_minute))); 624 625 // TODO: theianchen add code for filtering by time here. 626 } 627 628 /** 629 * A class representing a given time, e.g., "in the last hour". 630 */ 631 private static class TimeFilterItem { 632 private final long mTime; 633 private final @NonNull String mLabel; 634 TimeFilterItem(long time, @NonNull String label)635 TimeFilterItem(long time, @NonNull String label) { 636 mTime = time; 637 mLabel = label; 638 } 639 640 /** 641 * Get the time represented by this object in milliseconds. 642 * 643 * @return the time represented by this object. 644 */ getTime()645 public long getTime() { 646 return mTime; 647 } 648 getLabel()649 public @NonNull String getLabel() { 650 return mLabel; 651 } 652 } 653 654 /** 655 * A class representing an app usage entry in Permission Usage. 656 */ 657 private static class AppPermissionUsageEntry { 658 private final AppPermissionUsage mAppPermissionUsage; 659 private final List<Triple<Long, Long, OpEventProxyInfo>> mClusteredAccessTimeList; 660 private long mEndTime; 661 AppPermissionUsageEntry(AppPermissionUsage appPermissionUsage, long endTime, List<Triple<Long, Long, OpEventProxyInfo>> clusteredAccessTimeList)662 AppPermissionUsageEntry(AppPermissionUsage appPermissionUsage, long endTime, 663 List<Triple<Long, Long, OpEventProxyInfo>> clusteredAccessTimeList) { 664 mAppPermissionUsage = appPermissionUsage; 665 mEndTime = endTime; 666 mClusteredAccessTimeList = clusteredAccessTimeList; 667 } 668 getAppPermissionUsage()669 public AppPermissionUsage getAppPermissionUsage() { 670 return mAppPermissionUsage; 671 } 672 getEndTime()673 public long getEndTime() { 674 return mEndTime; 675 } 676 getAccessTime()677 public List<Triple<Long, Long, OpEventProxyInfo>> getAccessTime() { 678 return mClusteredAccessTimeList; 679 } 680 } 681 } 682