1 /* 2 * Copyright (C) 2019 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.server.telecom; 18 19 import android.annotation.IntRange; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.app.UiModeManager; 23 import android.telecom.Log; 24 import android.util.LocalLog; 25 26 import com.android.internal.util.IndentingPrintWriter; 27 28 import java.util.Comparator; 29 import java.util.List; 30 import java.util.Objects; 31 import java.util.PriorityQueue; 32 import java.util.stream.Collectors; 33 34 /** 35 * Tracks the package names of apps which enter end exit car mode. 36 */ 37 public class CarModeTracker { 38 /** 39 * Data class holding information about apps which have requested to enter car mode. 40 */ 41 private class CarModeApp { 42 private @IntRange(from = 0) int mPriority; 43 private @NonNull String mPackageName; 44 CarModeApp(int priority, @NonNull String packageName)45 public CarModeApp(int priority, @NonNull String packageName) { 46 mPriority = priority; 47 mPackageName = Objects.requireNonNull(packageName); 48 } 49 50 /** 51 * The priority at which the app requested to enter car mode. 52 * Will be the same as the one specified when {@link UiModeManager#enableCarMode(int, int)} 53 * was called, or {@link UiModeManager#DEFAULT_PRIORITY} if no priority was specifeid. 54 * @return The priority. 55 */ getPriority()56 public int getPriority() { 57 return mPriority; 58 } 59 setPriority(int priority)60 public void setPriority(int priority) { 61 mPriority = priority; 62 } 63 64 /** 65 * @return The package name of the app which requested to enter car mode. 66 */ getPackageName()67 public String getPackageName() { 68 return mPackageName; 69 } 70 setPackageName(String packageName)71 public void setPackageName(String packageName) { 72 mPackageName = packageName; 73 } 74 } 75 76 /** 77 * Comparator used to maintain the car mode priority queue ordering. 78 */ 79 private class CarModeAppComparator implements Comparator<CarModeApp> { 80 @Override compare(CarModeApp o1, CarModeApp o2)81 public int compare(CarModeApp o1, CarModeApp o2) { 82 // highest priority takes precedence. 83 return Integer.compare(o2.getPriority(), o1.getPriority()); 84 } 85 } 86 87 /** 88 * Priority list of apps which have entered or exited car mode, ordered with the highest 89 * priority app at the top of the queue. Where items have the same priority, they are ordered 90 * by insertion time. 91 */ 92 private PriorityQueue<CarModeApp> mCarModeApps = new PriorityQueue<>(2, 93 new CarModeAppComparator()); 94 95 private final LocalLog mCarModeChangeLog = new LocalLog(20); 96 97 /** 98 * Handles a request to enter car mode by a package name. 99 * @param priority The priority at which car mode is entered. 100 * @param packageName The package name of the app entering car mode. 101 */ handleEnterCarMode(@ntRangefrom = 0) int priority, @NonNull String packageName)102 public void handleEnterCarMode(@IntRange(from = 0) int priority, @NonNull String packageName) { 103 if (mCarModeApps.stream().anyMatch(c -> c.getPriority() == priority)) { 104 Log.w(this, "handleEnterCarMode: already in car mode at priority %d (apps: %s)", 105 priority, getCarModePriorityString()); 106 return; 107 } 108 109 if (mCarModeApps.stream().anyMatch(c -> c.getPackageName().equals(packageName))) { 110 Log.w(this, "handleEnterCarMode: %s is already in car mode (apps: %s)", 111 packageName, getCarModePriorityString()); 112 return; 113 } 114 115 Log.i(this, "handleEnterCarMode: packageName=%s, priority=%d", packageName, priority); 116 mCarModeChangeLog.log("enterCarMode: packageName=" + packageName + ", priority=" 117 + priority); 118 mCarModeApps.add(new CarModeApp(priority, packageName)); 119 } 120 121 /** 122 * Handles a request to exist car mode at a priority level. 123 * @param priority The priority level. 124 * @param packageName The packagename of the app requesting the change. 125 */ handleExitCarMode(@ntRangefrom = 0) int priority, @NonNull String packageName)126 public void handleExitCarMode(@IntRange(from = 0) int priority, @NonNull String packageName) { 127 if (!mCarModeApps.stream().anyMatch(c -> c.getPriority() == priority)) { 128 Log.w(this, "handleExitCarMode: not in car mode at priority %d (apps=%s)", 129 priority, getCarModePriorityString()); 130 return; 131 } 132 133 if (priority != UiModeManager.DEFAULT_PRIORITY && !mCarModeApps.stream().anyMatch( 134 c -> c.getPackageName().equals(packageName) && c.getPriority() == priority)) { 135 Log.w(this, "handleExitCarMode: %s didn't enter car mode at priority %d (apps=%s)", 136 packageName, priority, getCarModePriorityString()); 137 return; 138 } 139 140 Log.i(this, "handleExitCarMode: packageName=%s, priority=%d", packageName, priority); 141 mCarModeChangeLog.log("exitCarMode: packageName=" + packageName + ", priority=" 142 + priority); 143 mCarModeApps.removeIf(c -> c.getPriority() == priority); 144 } 145 146 /** 147 * Retrieves a list of the apps which are currently in car mode, ordered by priority such that 148 * the highest priority app is first. 149 * @return List of apps in car mode. 150 */ getCarModeApps()151 public @NonNull List<String> getCarModeApps() { 152 return mCarModeApps 153 .stream() 154 .sorted(mCarModeApps.comparator()) 155 .map(cma -> cma.getPackageName()) 156 .collect(Collectors.toList()); 157 } 158 getCarModePriorityString()159 private @NonNull String getCarModePriorityString() { 160 return mCarModeApps 161 .stream() 162 .sorted(mCarModeApps.comparator()) 163 .map(cma -> "[" + cma.getPriority() + ", " + cma.getPackageName() + "]") 164 .collect(Collectors.joining(", ")); 165 } 166 167 /** 168 * Gets the app which is currently in car mode. This is the highest priority app which has 169 * entered car mode. 170 * @return The app which is in car mode. 171 */ getCurrentCarModePackage()172 public @Nullable String getCurrentCarModePackage() { 173 CarModeApp app = mCarModeApps.peek(); 174 return app == null ? null : app.getPackageName(); 175 } 176 177 /** 178 * @return {@code true} if the device is in car mode, {@code false} otherwise. 179 */ isInCarMode()180 public boolean isInCarMode() { 181 return !mCarModeApps.isEmpty(); 182 } 183 184 /** 185 * Dumps the state of the car mode tracker to the specified print writer. 186 * @param pw 187 */ dump(IndentingPrintWriter pw)188 public void dump(IndentingPrintWriter pw) { 189 pw.println("CarModeTracker:"); 190 pw.increaseIndent(); 191 192 pw.println("Current car mode apps:"); 193 pw.increaseIndent(); 194 for (CarModeApp app : mCarModeApps) { 195 pw.print("["); 196 pw.print(app.getPriority()); 197 pw.print("] "); 198 pw.println(app.getPackageName()); 199 } 200 pw.decreaseIndent(); 201 202 pw.println("Car mode history:"); 203 pw.increaseIndent(); 204 mCarModeChangeLog.dump(pw); 205 pw.decreaseIndent(); 206 207 pw.decreaseIndent(); 208 } 209 } 210