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.car.pm; 18 19 import android.annotation.IntDef; 20 import android.annotation.Nullable; 21 import android.content.ComponentName; 22 import android.content.Intent; 23 import android.text.TextUtils; 24 25 import com.android.internal.annotations.VisibleForTesting; 26 27 import java.lang.annotation.Retention; 28 import java.lang.annotation.RetentionPolicy; 29 30 /** 31 * Describes configuration of the vendor service that needs to be started by Car Service. This is 32 * immutable object to store only service configuration. 33 */ 34 final class VendorServiceInfo { 35 36 @VisibleForTesting 37 static final int DEFAULT_MAX_RETRIES = 6; 38 39 private static final String KEY_BIND = "bind"; 40 private static final String KEY_USER_SCOPE = "user"; 41 private static final String KEY_TRIGGER = "trigger"; 42 private static final String KEY_MAX_RETRIES = "maxRetries"; 43 44 private static final int USER_SCOPE_ALL = 0; 45 private static final int USER_SCOPE_SYSTEM = 1; 46 private static final int USER_SCOPE_FOREGROUND = 2; 47 private static final int USER_SCOPE_VISIBLE = 3; 48 private static final int USER_SCOPE_BACKGROUND_VISIBLE = 4; 49 @Retention(RetentionPolicy.SOURCE) 50 @IntDef({ 51 USER_SCOPE_ALL, 52 USER_SCOPE_FOREGROUND, 53 USER_SCOPE_SYSTEM, 54 USER_SCOPE_VISIBLE, 55 USER_SCOPE_BACKGROUND_VISIBLE, 56 }) 57 @interface UserScope {} 58 59 private static final int TRIGGER_ASAP = 0; 60 private static final int TRIGGER_UNLOCKED = 1; 61 private static final int TRIGGER_POST_UNLOCKED = 2; 62 private static final int TRIGGER_RESUME = 3; 63 @Retention(RetentionPolicy.SOURCE) 64 @IntDef({ 65 TRIGGER_ASAP, 66 TRIGGER_UNLOCKED, 67 TRIGGER_POST_UNLOCKED, 68 TRIGGER_RESUME, 69 }) 70 @interface Trigger {} 71 72 private static final int BIND = 0; 73 private static final int START = 1; 74 private static final int START_FOREGROUND = 2; 75 @Retention(RetentionPolicy.SOURCE) 76 @IntDef({ 77 BIND, 78 START, 79 START_FOREGROUND, 80 }) 81 @interface Bind {} 82 83 private final @Bind int mBind; 84 private final @UserScope int mUserScope; 85 private final @Trigger int mTrigger; 86 private final int mMaxRetries; 87 @Nullable 88 private final ComponentName mComponentName; 89 VendorServiceInfo(@ullable ComponentName componentName, @Bind int bind, @UserScope int userScope, @Trigger int trigger, int maxRetries)90 private VendorServiceInfo(@Nullable ComponentName componentName, @Bind int bind, 91 @UserScope int userScope, @Trigger int trigger, int maxRetries) { 92 mComponentName = componentName; 93 mUserScope = userScope; 94 mTrigger = trigger; 95 mBind = bind; 96 mMaxRetries = maxRetries; 97 } 98 isAllUserService()99 boolean isAllUserService() { 100 return mUserScope == USER_SCOPE_ALL; 101 } 102 isSystemUserService()103 boolean isSystemUserService() { 104 return mUserScope == USER_SCOPE_ALL || mUserScope == USER_SCOPE_SYSTEM; 105 } 106 isForegroundUserService()107 boolean isForegroundUserService() { 108 return mUserScope == USER_SCOPE_ALL || mUserScope == USER_SCOPE_FOREGROUND; 109 } 110 isVisibleUserService()111 boolean isVisibleUserService() { 112 return mUserScope == USER_SCOPE_ALL || mUserScope == USER_SCOPE_VISIBLE; 113 } 114 isBackgroundVisibleUserService()115 boolean isBackgroundVisibleUserService() { 116 return mUserScope == USER_SCOPE_ALL || mUserScope == USER_SCOPE_BACKGROUND_VISIBLE; 117 } 118 shouldStartOnUnlock()119 boolean shouldStartOnUnlock() { 120 return mTrigger == TRIGGER_UNLOCKED; 121 } 122 shouldStartOnPostUnlock()123 boolean shouldStartOnPostUnlock() { 124 return mTrigger == TRIGGER_POST_UNLOCKED; 125 } 126 shouldStartOnResume()127 boolean shouldStartOnResume() { 128 return mTrigger == TRIGGER_RESUME; 129 } 130 shouldStartAsap()131 boolean shouldStartAsap() { 132 return mTrigger == TRIGGER_ASAP; 133 } 134 shouldBeBound()135 boolean shouldBeBound() { 136 return mBind == BIND; 137 } 138 shouldBeStartedInForeground()139 boolean shouldBeStartedInForeground() { 140 return mBind == START_FOREGROUND; 141 } 142 getMaxRetries()143 int getMaxRetries() { 144 return mMaxRetries; 145 } 146 getIntent()147 Intent getIntent() { 148 Intent intent = new Intent(); 149 intent.setComponent(mComponentName); 150 return intent; 151 } 152 parse(String rawServiceInfo)153 static VendorServiceInfo parse(String rawServiceInfo) { 154 String[] serviceParamTokens = rawServiceInfo.split("#"); 155 if (serviceParamTokens.length < 1 || serviceParamTokens.length > 2) { 156 throw new IllegalArgumentException("Failed to parse service info: " 157 + rawServiceInfo + ", expected a single '#' symbol"); 158 159 } 160 161 final ComponentName cn = ComponentName.unflattenFromString(serviceParamTokens[0]); 162 if (cn == null) { 163 throw new IllegalArgumentException("Failed to unflatten component name from: " 164 + rawServiceInfo); 165 } 166 167 int bind = START; 168 int userScope = USER_SCOPE_ALL; 169 int trigger = TRIGGER_UNLOCKED; 170 int maxRetries = DEFAULT_MAX_RETRIES; 171 172 if (serviceParamTokens.length == 2) { 173 for (String keyValueStr : serviceParamTokens[1].split(",")) { 174 String[] pair = keyValueStr.split("="); 175 String key = pair[0]; 176 String val = pair[1]; 177 if (TextUtils.isEmpty(key)) { 178 continue; 179 } 180 181 switch (key) { 182 case KEY_BIND: 183 switch (val) { 184 case "bind": 185 bind = BIND; 186 break; 187 case "start": 188 bind = START; 189 break; 190 case "startForeground": 191 bind = START_FOREGROUND; 192 break; 193 default: 194 throw new IllegalArgumentException("Unexpected bind option: " 195 + val); 196 } 197 break; 198 case KEY_USER_SCOPE: 199 switch (val) { 200 case "all": 201 userScope = USER_SCOPE_ALL; 202 break; 203 case "system": 204 userScope = USER_SCOPE_SYSTEM; 205 break; 206 case "foreground": 207 userScope = USER_SCOPE_FOREGROUND; 208 break; 209 case "visible": 210 userScope = USER_SCOPE_VISIBLE; 211 break; 212 case "backgroundVisible": 213 userScope = USER_SCOPE_BACKGROUND_VISIBLE; 214 break; 215 default: 216 throw new IllegalArgumentException("Unexpected user scope: " + val); 217 } 218 break; 219 case KEY_TRIGGER: 220 switch (val) { 221 case "asap": 222 trigger = TRIGGER_ASAP; 223 break; 224 case "userUnlocked": 225 trigger = TRIGGER_UNLOCKED; 226 break; 227 case "userPostUnlocked": 228 trigger = TRIGGER_POST_UNLOCKED; 229 break; 230 case "resume": 231 trigger = TRIGGER_RESUME; 232 break; 233 default: 234 throw new IllegalArgumentException("Unexpected trigger: " + val); 235 } 236 break; 237 case KEY_MAX_RETRIES: 238 try { 239 maxRetries = Integer.parseInt(val); 240 } catch (NumberFormatException e) { 241 throw new IllegalArgumentException( 242 "Cannot parse the specified `maxRetries` into an integer: " 243 + val); 244 } 245 break; 246 default: 247 throw new IllegalArgumentException("Unexpected token: " + key); 248 } 249 } 250 } 251 252 return new VendorServiceInfo(cn, bind, userScope, trigger, maxRetries); 253 } 254 255 @Override toString()256 public String toString() { 257 return "VendorService{" 258 + "component=" + toShortString() 259 + ", bind=" + bindToString(mBind) 260 + ", trigger=" + triggerToString(mTrigger) 261 + ", userScope=" + userScopeToString(mUserScope) 262 + (mMaxRetries == DEFAULT_MAX_RETRIES ? "" : ", maxRetries=" + mMaxRetries) 263 + '}'; 264 } 265 toShortString()266 String toShortString() { 267 return mComponentName != null ? mComponentName.flattenToShortString() : "N/A"; 268 } 269 270 // NOTE: cannot use DebugUtils below because constants are private 271 bindToString(@ind int bind)272 private static String bindToString(@Bind int bind) { 273 switch (bind) { 274 case BIND: 275 return "BIND"; 276 case START: 277 return "START"; 278 case START_FOREGROUND: 279 return "START_FOREGROUND"; 280 default: 281 return "INVALID-" + bind; 282 } 283 } 284 triggerToString(@rigger int trigger)285 private static String triggerToString(@Trigger int trigger) { 286 switch (trigger) { 287 case TRIGGER_ASAP: 288 return "ASAP"; 289 case TRIGGER_UNLOCKED: 290 return "UNLOCKED"; 291 case TRIGGER_POST_UNLOCKED: 292 return "POST_UNLOCKED"; 293 case TRIGGER_RESUME: 294 return "RESUME"; 295 default: 296 return "INVALID-" + trigger; 297 } 298 } 299 userScopeToString(@serScope int userScope)300 private static String userScopeToString(@UserScope int userScope) { 301 switch (userScope) { 302 case USER_SCOPE_ALL: 303 return "ALL"; 304 case USER_SCOPE_FOREGROUND: 305 return "FOREGROUND"; 306 case USER_SCOPE_SYSTEM: 307 return "SYSTEM"; 308 case USER_SCOPE_VISIBLE: 309 return "VISIBLE"; 310 case USER_SCOPE_BACKGROUND_VISIBLE: 311 return "BACKGROUND_VISIBLE"; 312 default: 313 return "INVALID-" + userScope; 314 } 315 } 316 } 317