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 java.lang.annotation.Retention; 26 import java.lang.annotation.RetentionPolicy; 27 28 /** 29 * Describes configuration of the vendor service that needs to be started by Car Service. This is 30 * immutable object to store only service configuration. 31 */ 32 final class VendorServiceInfo { 33 private static final String KEY_BIND = "bind"; 34 private static final String KEY_USER_SCOPE = "user"; 35 private static final String KEY_TRIGGER = "trigger"; 36 37 private static final int USER_SCOPE_ALL = 0; 38 private static final int USER_SCOPE_SYSTEM = 1; 39 private static final int USER_SCOPE_FOREGROUND = 2; 40 @Retention(RetentionPolicy.SOURCE) 41 @IntDef({ 42 USER_SCOPE_ALL, 43 USER_SCOPE_FOREGROUND, 44 USER_SCOPE_SYSTEM, 45 }) 46 @interface UserScope {} 47 48 private static final int TRIGGER_ASAP = 0; 49 private static final int TRIGGER_UNLOCKED = 1; 50 private static final int TRIGGER_POST_UNLOCKED = 2; 51 @Retention(RetentionPolicy.SOURCE) 52 @IntDef({ 53 TRIGGER_ASAP, 54 TRIGGER_UNLOCKED, 55 TRIGGER_POST_UNLOCKED, 56 }) 57 @interface Trigger {} 58 59 private static final int BIND = 0; 60 private static final int START = 1; 61 private static final int START_FOREGROUND = 2; 62 @Retention(RetentionPolicy.SOURCE) 63 @IntDef({ 64 BIND, 65 START, 66 START_FOREGROUND, 67 }) 68 @interface Bind {} 69 70 private final @Bind int mBind; 71 private final @UserScope int mUserScope; 72 private final @Trigger int mTrigger; 73 @Nullable 74 private final ComponentName mComponentName; 75 VendorServiceInfo(@ullable ComponentName componentName, @Bind int bind, @UserScope int userScope, @Trigger int trigger)76 private VendorServiceInfo(@Nullable ComponentName componentName, @Bind int bind, 77 @UserScope int userScope, @Trigger int trigger) { 78 mComponentName = componentName; 79 mUserScope = userScope; 80 mTrigger = trigger; 81 mBind = bind; 82 } 83 isSystemUserService()84 boolean isSystemUserService() { 85 return mUserScope == USER_SCOPE_ALL || mUserScope == USER_SCOPE_SYSTEM; 86 } 87 isForegroundUserService()88 boolean isForegroundUserService() { 89 return mUserScope == USER_SCOPE_ALL || mUserScope == USER_SCOPE_FOREGROUND; 90 } 91 shouldStartOnUnlock()92 boolean shouldStartOnUnlock() { 93 return mTrigger == TRIGGER_UNLOCKED; 94 } 95 shouldStartOnPostUnlock()96 boolean shouldStartOnPostUnlock() { 97 return mTrigger == TRIGGER_POST_UNLOCKED; 98 } 99 shouldStartAsap()100 boolean shouldStartAsap() { 101 return mTrigger == TRIGGER_ASAP; 102 } 103 shouldBeBound()104 boolean shouldBeBound() { 105 return mBind == BIND; 106 } 107 shouldBeStartedInForeground()108 boolean shouldBeStartedInForeground() { 109 return mBind == START_FOREGROUND; 110 } 111 getIntent()112 Intent getIntent() { 113 Intent intent = new Intent(); 114 intent.setComponent(mComponentName); 115 return intent; 116 } 117 parse(String rawServiceInfo)118 static VendorServiceInfo parse(String rawServiceInfo) { 119 String[] serviceParamTokens = rawServiceInfo.split("#"); 120 if (serviceParamTokens.length < 1 || serviceParamTokens.length > 2) { 121 throw new IllegalArgumentException("Failed to parse service info: " 122 + rawServiceInfo + ", expected a single '#' symbol"); 123 124 } 125 126 final ComponentName cn = ComponentName.unflattenFromString(serviceParamTokens[0]); 127 if (cn == null) { 128 throw new IllegalArgumentException("Failed to unflatten component name from: " 129 + rawServiceInfo); 130 } 131 132 int bind = START; 133 int userScope = USER_SCOPE_ALL; 134 int trigger = TRIGGER_UNLOCKED; 135 136 if (serviceParamTokens.length == 2) { 137 for (String keyValueStr : serviceParamTokens[1].split(",")) { 138 String[] pair = keyValueStr.split("="); 139 String key = pair[0]; 140 String val = pair[1]; 141 if (TextUtils.isEmpty(key)) { 142 continue; 143 } 144 145 switch (key) { 146 case KEY_BIND: 147 switch (val) { 148 case "bind": 149 bind = BIND; 150 break; 151 case "start": 152 bind = START; 153 break; 154 case "startForeground": 155 bind = START_FOREGROUND; 156 break; 157 default: 158 throw new IllegalArgumentException("Unexpected bind option: " 159 + val); 160 } 161 break; 162 case KEY_USER_SCOPE: 163 switch (val) { 164 case "all": 165 userScope = USER_SCOPE_ALL; 166 break; 167 case "system": 168 userScope = USER_SCOPE_SYSTEM; 169 break; 170 case "foreground": 171 userScope = USER_SCOPE_FOREGROUND; 172 break; 173 default: 174 throw new IllegalArgumentException("Unexpected user scope: " + val); 175 } 176 break; 177 case KEY_TRIGGER: 178 switch (val) { 179 case "asap": 180 trigger = TRIGGER_ASAP; 181 break; 182 case "userUnlocked": 183 trigger = TRIGGER_UNLOCKED; 184 break; 185 case "userPostUnlocked": 186 trigger = TRIGGER_POST_UNLOCKED; 187 break; 188 default: 189 throw new IllegalArgumentException("Unexpected trigger: " + val); 190 } 191 break; 192 default: 193 throw new IllegalArgumentException("Unexpected token: " + key); 194 } 195 } 196 } 197 198 return new VendorServiceInfo(cn, bind, userScope, trigger); 199 } 200 201 @Override toString()202 public String toString() { 203 return "VendorService{" 204 + "component=" + toShortString() 205 + ", bind=" + bindToString(mBind) 206 + ", trigger=" + triggerToString(mTrigger) 207 + ", userScope=" + userScopeToString(mUserScope) 208 + '}'; 209 } 210 toShortString()211 String toShortString() { 212 return mComponentName != null ? mComponentName.flattenToShortString() : "N/A"; 213 } 214 215 // NOTE: cannot use DebugUtils below because constants are private 216 bindToString(@ind int bind)217 private static String bindToString(@Bind int bind) { 218 switch (bind) { 219 case BIND: 220 return "BIND"; 221 case START: 222 return "START"; 223 case START_FOREGROUND: 224 return "START_FOREGROUND"; 225 default: 226 return "INVALID-" + bind; 227 } 228 } 229 triggerToString(@rigger int trigger)230 private static String triggerToString(@Trigger int trigger) { 231 switch (trigger) { 232 case TRIGGER_ASAP: 233 return "ASAP"; 234 case TRIGGER_UNLOCKED: 235 return "UNLOCKED"; 236 case TRIGGER_POST_UNLOCKED: 237 return "POST_UNLOCKED"; 238 default: 239 return "INVALID-" + trigger; 240 } 241 } 242 userScopeToString(@serScope int userScope)243 private static String userScopeToString(@UserScope int userScope) { 244 switch (userScope) { 245 case USER_SCOPE_ALL: 246 return "ALL"; 247 case USER_SCOPE_FOREGROUND: 248 return "FOREGROUND"; 249 case USER_SCOPE_SYSTEM: 250 return "SYSTEM"; 251 default: 252 return "INVALID-" + userScope; 253 } 254 } 255 } 256