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