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.content.ComponentName;
21 import android.content.Intent;
22 import android.text.TextUtils;
23 
24 import java.lang.annotation.Retention;
25 import java.lang.annotation.RetentionPolicy;
26 
27 /**
28  * Describes configuration of the vendor service that needs to be started by Car Service. This is
29  * immutable object to store only service configuration.
30  */
31 class VendorServiceInfo {
32     private static final String KEY_BIND = "bind";
33     private static final String KEY_USER_SCOPE = "user";
34     private static final String KEY_TRIGGER = "trigger";
35 
36     private static final int USER_SCOPE_ALL = 0;
37     private static final int USER_SCOPE_SYSTEM = 1;
38     private static final int USER_SCOPE_FOREGROUND = 2;
39     @Retention(RetentionPolicy.SOURCE)
40     @IntDef({
41             USER_SCOPE_ALL,
42             USER_SCOPE_FOREGROUND,
43             USER_SCOPE_SYSTEM,
44     })
45     @interface UserScope {}
46 
47     private static final int TRIGGER_ASAP = 0;
48     private static final int TRIGGER_UNLOCKED = 1;
49     @Retention(RetentionPolicy.SOURCE)
50     @IntDef({
51             TRIGGER_ASAP,
52             TRIGGER_UNLOCKED,
53     })
54     @interface Trigger {}
55 
56     private static final int BIND = 0;
57     private static final int START = 1;
58     private static final int START_FOREGROUND = 2;
59     @Retention(RetentionPolicy.SOURCE)
60     @IntDef({
61             BIND,
62             START,
63             START_FOREGROUND,
64     })
65     @interface Bind {}
66 
67     private final @Bind int mBind;
68     private final @UserScope int mUserScope;
69     private final @Trigger int mTrigger;
70     private final ComponentName mComponentName;
71 
VendorServiceInfo(ComponentName componentName, @Bind int bind, @UserScope int userScope, @Trigger int trigger)72     private VendorServiceInfo(ComponentName componentName, @Bind int bind, @UserScope int userScope,
73             @Trigger int trigger) {
74         mComponentName = componentName;
75         mUserScope = userScope;
76         mTrigger = trigger;
77         mBind = bind;
78     }
79 
isSystemUserService()80     boolean isSystemUserService() {
81         return mUserScope == USER_SCOPE_ALL || mUserScope == USER_SCOPE_SYSTEM;
82     }
83 
isForegroundUserService()84     boolean isForegroundUserService() {
85         return mUserScope == USER_SCOPE_ALL || mUserScope == USER_SCOPE_FOREGROUND;
86     }
87 
shouldStartOnUnlock()88     boolean shouldStartOnUnlock() {
89         return mTrigger == TRIGGER_UNLOCKED;
90     }
91 
shouldStartAsap()92     boolean shouldStartAsap() {
93         return mTrigger == TRIGGER_ASAP;
94     }
95 
shouldBeBound()96     boolean shouldBeBound() {
97         return mBind == BIND;
98     }
99 
shouldBeStartedInForeground()100     boolean shouldBeStartedInForeground() {
101         return mBind == START_FOREGROUND;
102     }
103 
getIntent()104     Intent getIntent() {
105         Intent intent = new Intent();
106         intent.setComponent(mComponentName);
107         return intent;
108     }
109 
parse(String rawServiceInfo)110     static VendorServiceInfo parse(String rawServiceInfo) {
111         String[] serviceParamTokens = rawServiceInfo.split("#");
112         if (serviceParamTokens.length < 1 || serviceParamTokens.length > 2) {
113             throw new IllegalArgumentException("Failed to parse service info: "
114                     + rawServiceInfo + ", expected a single '#' symbol");
115 
116         }
117 
118         final ComponentName cn = ComponentName.unflattenFromString(serviceParamTokens[0]);
119         if (cn == null) {
120             throw new IllegalArgumentException("Failed to unflatten component name from: "
121                     + rawServiceInfo);
122         }
123 
124         int bind = START;
125         int userScope = USER_SCOPE_ALL;
126         int trigger = TRIGGER_UNLOCKED;
127 
128         if (serviceParamTokens.length == 2) {
129             for (String keyValueStr : serviceParamTokens[1].split(",")) {
130                 String[] pair = keyValueStr.split("=");
131                 String key = pair[0];
132                 String val = pair[1];
133                 if (TextUtils.isEmpty(key)) {
134                     continue;
135                 }
136 
137                 switch (key) {
138                     case KEY_BIND:
139                         if ("bind".equals(val)) {
140                             bind = BIND;
141                         } else if ("start".equals(val)) {
142                             bind = START;
143                         } else if ("startForeground".equals(val)) {
144                             bind = START_FOREGROUND;
145                         } else {
146                             throw new IllegalArgumentException("Unexpected bind option: " + val);
147                         }
148                         break;
149                     case KEY_USER_SCOPE:
150                         if ("all".equals(val)) {
151                             userScope = USER_SCOPE_ALL;
152                         } else if ("system".equals(val)) {
153                             userScope = USER_SCOPE_SYSTEM;
154                         } else if ("foreground".equals(val)) {
155                             userScope = USER_SCOPE_FOREGROUND;
156                         } else {
157                             throw new IllegalArgumentException("Unexpected user scope: " + val);
158                         }
159                         break;
160                     case KEY_TRIGGER:
161                         if ("asap".equals(val)) {
162                             trigger = TRIGGER_ASAP;
163                         } else if ("userUnlocked".equals(val)) {
164                             trigger = TRIGGER_UNLOCKED;
165                         } else {
166                             throw new IllegalArgumentException("Unexpected trigger: " + val);
167                         }
168                         break;
169                     default:
170                         throw new IllegalArgumentException("Unexpected token: " + key);
171                 }
172             }
173         }
174 
175         return new VendorServiceInfo(cn, bind, userScope, trigger);
176     }
177 
178     @Override
toString()179     public String toString() {
180         return "VendorService{"
181                 + "component=" + mComponentName
182                 + ", bind=" + mBind
183                 + ", trigger=" + mTrigger
184                 + ", user=" + mUserScope
185                 + '}';
186     }
187 
toShortString()188     String toShortString() {
189         return mComponentName != null ? mComponentName.toShortString() : "";
190     }
191 }
192