1/**
2 * Copyright (c) 2014, 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
17package com.android.server.notification;
18
19import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT;
20import static android.content.Context.BIND_AUTO_CREATE;
21import static android.content.Context.BIND_FOREGROUND_SERVICE;
22import static android.content.Context.DEVICE_POLICY_SERVICE;
23
24import android.annotation.NonNull;
25import android.app.ActivityManager;
26import android.app.PendingIntent;
27import android.app.admin.DevicePolicyManager;
28import android.content.ComponentName;
29import android.content.ContentResolver;
30import android.content.Context;
31import android.content.Intent;
32import android.content.ServiceConnection;
33import android.content.pm.ApplicationInfo;
34import android.content.pm.IPackageManager;
35import android.content.pm.PackageManager;
36import android.content.pm.PackageManager.NameNotFoundException;
37import android.content.pm.ResolveInfo;
38import android.content.pm.ServiceInfo;
39import android.content.pm.UserInfo;
40import android.os.Binder;
41import android.os.Build;
42import android.os.Handler;
43import android.os.IBinder;
44import android.os.IInterface;
45import android.os.Looper;
46import android.os.RemoteException;
47import android.os.UserHandle;
48import android.os.UserManager;
49import android.provider.Settings;
50import android.service.notification.ManagedServiceInfoProto;
51import android.service.notification.ManagedServicesProto;
52import android.service.notification.ManagedServicesProto.ServiceProto;
53import android.text.TextUtils;
54import android.util.ArrayMap;
55import android.util.ArraySet;
56import android.util.Log;
57import android.util.Slog;
58import android.util.SparseArray;
59import android.util.proto.ProtoOutputStream;
60
61import com.android.internal.util.XmlUtils;
62import com.android.server.notification.NotificationManagerService.DumpFilter;
63
64import org.xmlpull.v1.XmlPullParser;
65import org.xmlpull.v1.XmlPullParserException;
66import org.xmlpull.v1.XmlSerializer;
67
68import java.io.IOException;
69import java.io.PrintWriter;
70import java.util.ArrayList;
71import java.util.Arrays;
72import java.util.HashSet;
73import java.util.List;
74import java.util.Set;
75import java.util.function.Predicate;
76import java.util.stream.Collectors;
77
78/**
79 * Manages the lifecycle of application-provided services bound by system server.
80 *
81 * Services managed by this helper must have:
82 *  - An associated system settings value with a list of enabled component names.
83 *  - A well-known action for services to use in their intent-filter.
84 *  - A system permission for services to require in order to ensure system has exclusive binding.
85 *  - A settings page for user configuration of enabled services, and associated intent action.
86 *  - A remote interface definition (aidl) provided by the service used for communication.
87 */
88abstract public class ManagedServices {
89    protected final String TAG = getClass().getSimpleName();
90    protected final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
91
92    private static final int ON_BINDING_DIED_REBIND_DELAY_MS = 10000;
93    protected static final String ENABLED_SERVICES_SEPARATOR = ":";
94
95    /**
96     * List of components and apps that can have running {@link ManagedServices}.
97     */
98    static final String TAG_MANAGED_SERVICES = "service_listing";
99    static final String ATT_APPROVED_LIST = "approved";
100    static final String ATT_USER_ID = "user";
101    static final String ATT_IS_PRIMARY = "primary";
102    static final String ATT_VERSION = "version";
103
104    static final int DB_VERSION = 1;
105
106    static final int APPROVAL_BY_PACKAGE = 0;
107    static final int APPROVAL_BY_COMPONENT = 1;
108
109    protected final Context mContext;
110    protected final Object mMutex;
111    private final UserProfiles mUserProfiles;
112    private final IPackageManager mPm;
113    protected final UserManager mUm;
114    private final Config mConfig;
115    private final Handler mHandler = new Handler(Looper.getMainLooper());
116
117    // contains connections to all connected services, including app services
118    // and system services
119    private final ArrayList<ManagedServiceInfo> mServices = new ArrayList<>();
120    // things that will be put into mServices as soon as they're ready
121    private final ArrayList<String> mServicesBinding = new ArrayList<>();
122    private final ArraySet<String> mServicesRebinding = new ArraySet<>();
123
124    // lists the component names of all enabled (and therefore potentially connected)
125    // app services for current profiles.
126    private ArraySet<ComponentName> mEnabledServicesForCurrentProfiles
127            = new ArraySet<>();
128    // Just the packages from mEnabledServicesForCurrentProfiles
129    private ArraySet<String> mEnabledServicesPackageNames = new ArraySet<>();
130    // List of enabled packages that have nevertheless asked not to be run
131    private ArraySet<ComponentName> mSnoozingForCurrentProfiles = new ArraySet<>();
132
133    // List of approved packages or components (by user, then by primary/secondary) that are
134    // allowed to be bound as managed services. A package or component appearing in this list does
135    // not mean that we are currently bound to said package/component.
136    private ArrayMap<Integer, ArrayMap<Boolean, ArraySet<String>>> mApproved = new ArrayMap<>();
137
138    // Kept to de-dupe user change events (experienced after boot, when we receive a settings and a
139    // user change).
140    private int[] mLastSeenProfileIds;
141
142    // True if approved services are stored in xml, not settings.
143    private boolean mUseXml;
144
145    // Whether managed services are approved individually or package wide
146    protected int mApprovalLevel;
147
148    public ManagedServices(Context context, Object mutex, UserProfiles userProfiles,
149            IPackageManager pm) {
150        mContext = context;
151        mMutex = mutex;
152        mUserProfiles = userProfiles;
153        mPm = pm;
154        mConfig = getConfig();
155        mApprovalLevel = APPROVAL_BY_COMPONENT;
156        mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
157    }
158
159    abstract protected Config getConfig();
160
161    private String getCaption() {
162        return mConfig.caption;
163    }
164
165    abstract protected IInterface asInterface(IBinder binder);
166
167    abstract protected boolean checkType(IInterface service);
168
169    abstract protected void onServiceAdded(ManagedServiceInfo info);
170
171    protected List<ManagedServiceInfo> getServices() {
172        synchronized (mMutex) {
173            List<ManagedServiceInfo> services = new ArrayList<>(mServices);
174            return services;
175        }
176    }
177
178    protected void onServiceRemovedLocked(ManagedServiceInfo removed) { }
179
180    private ManagedServiceInfo newServiceInfo(IInterface service,
181            ComponentName component, int userId, boolean isSystem, ServiceConnection connection,
182            int targetSdkVersion) {
183        return new ManagedServiceInfo(service, component, userId, isSystem, connection,
184                targetSdkVersion);
185    }
186
187    public void onBootPhaseAppsCanStart() {}
188
189    public void dump(PrintWriter pw, DumpFilter filter) {
190        pw.println("    Allowed " + getCaption() + "s:");
191        final int N = mApproved.size();
192        for (int i = 0 ; i < N; i++) {
193            final int userId = mApproved.keyAt(i);
194            final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.valueAt(i);
195            if (approvedByType != null) {
196                final int M = approvedByType.size();
197                for (int j = 0; j < M; j++) {
198                    final boolean isPrimary = approvedByType.keyAt(j);
199                    final ArraySet<String> approved = approvedByType.valueAt(j);
200                    if (approvedByType != null && approvedByType.size() > 0) {
201                        pw.println("      " + String.join(ENABLED_SERVICES_SEPARATOR, approved)
202                                + " (user: " + userId + " isPrimary: " + isPrimary + ")");
203                    }
204                }
205            }
206        }
207
208        pw.println("    All " + getCaption() + "s (" + mEnabledServicesForCurrentProfiles.size()
209                + ") enabled for current profiles:");
210        for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
211            if (filter != null && !filter.matches(cmpt)) continue;
212            pw.println("      " + cmpt);
213        }
214
215        pw.println("    Live " + getCaption() + "s (" + mServices.size() + "):");
216        for (ManagedServiceInfo info : mServices) {
217            if (filter != null && !filter.matches(info.component)) continue;
218            pw.println("      " + info.component
219                    + " (user " + info.userid + "): " + info.service
220                    + (info.isSystem?" SYSTEM":"")
221                    + (info.isGuest(this)?" GUEST":""));
222        }
223
224        pw.println("    Snoozed " + getCaption() + "s (" +
225                mSnoozingForCurrentProfiles.size() + "):");
226        for (ComponentName name : mSnoozingForCurrentProfiles) {
227            pw.println("      " + name.flattenToShortString());
228        }
229    }
230
231    public void dump(ProtoOutputStream proto, DumpFilter filter) {
232        proto.write(ManagedServicesProto.CAPTION, getCaption());
233        final int N = mApproved.size();
234        for (int i = 0 ; i < N; i++) {
235            final int userId = mApproved.keyAt(i);
236            final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.valueAt(i);
237            if (approvedByType != null) {
238                final int M = approvedByType.size();
239                for (int j = 0; j < M; j++) {
240                    final boolean isPrimary = approvedByType.keyAt(j);
241                    final ArraySet<String> approved = approvedByType.valueAt(j);
242                    if (approvedByType != null && approvedByType.size() > 0) {
243                        final long sToken = proto.start(ManagedServicesProto.APPROVED);
244                        for (String s : approved) {
245                            proto.write(ServiceProto.NAME, s);
246                        }
247                        proto.write(ServiceProto.USER_ID, userId);
248                        proto.write(ServiceProto.IS_PRIMARY, isPrimary);
249                        proto.end(sToken);
250                    }
251                }
252            }
253        }
254
255        for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
256            if (filter != null && !filter.matches(cmpt)) continue;
257            cmpt.writeToProto(proto, ManagedServicesProto.ENABLED);
258        }
259
260        for (ManagedServiceInfo info : mServices) {
261            if (filter != null && !filter.matches(info.component)) continue;
262            info.writeToProto(proto, ManagedServicesProto.LIVE_SERVICES, this);
263        }
264
265        for (ComponentName name : mSnoozingForCurrentProfiles) {
266            name.writeToProto(proto, ManagedServicesProto.SNOOZED);
267        }
268    }
269
270    protected void onSettingRestored(String element, String value, int backupSdkInt, int userId) {
271        if (!mUseXml) {
272            Slog.d(TAG, "Restored managed service setting: " + element);
273            if (mConfig.secureSettingName.equals(element) ||
274                    (mConfig.secondarySettingName != null
275                            && mConfig.secondarySettingName.equals(element))) {
276                if (backupSdkInt < Build.VERSION_CODES.O) {
277                    // automatic system grants were added in O, so append the approved apps
278                    // rather than wiping out the setting
279                    String currentSetting =
280                            getApproved(userId, mConfig.secureSettingName.equals(element));
281                    if (!TextUtils.isEmpty(currentSetting)) {
282                        if (!TextUtils.isEmpty(value)) {
283                            value = value + ENABLED_SERVICES_SEPARATOR + currentSetting;
284                        } else {
285                            value = currentSetting;
286                        }
287                    }
288                }
289                Settings.Secure.putStringForUser(
290                        mContext.getContentResolver(), element, value, userId);
291                loadAllowedComponentsFromSettings();
292                rebindServices(false);
293            }
294        }
295    }
296
297    public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
298        out.startTag(null, getConfig().xmlTag);
299
300        out.attribute(null, ATT_VERSION, String.valueOf(DB_VERSION));
301
302        if (forBackup) {
303            trimApprovedListsAccordingToInstalledServices();
304        }
305
306        final int N = mApproved.size();
307        for (int i = 0 ; i < N; i++) {
308            final int userId = mApproved.keyAt(i);
309            final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.valueAt(i);
310            if (approvedByType != null) {
311                final int M = approvedByType.size();
312                for (int j = 0; j < M; j++) {
313                    final boolean isPrimary = approvedByType.keyAt(j);
314                    final Set<String> approved = approvedByType.valueAt(j);
315                    if (approved != null) {
316                        String allowedItems = String.join(ENABLED_SERVICES_SEPARATOR, approved);
317                        out.startTag(null, TAG_MANAGED_SERVICES);
318                        out.attribute(null, ATT_APPROVED_LIST, allowedItems);
319                        out.attribute(null, ATT_USER_ID, Integer.toString(userId));
320                        out.attribute(null, ATT_IS_PRIMARY, Boolean.toString(isPrimary));
321                        out.endTag(null, TAG_MANAGED_SERVICES);
322
323                        if (!forBackup && isPrimary) {
324                            // Also write values to settings, for observers who haven't migrated yet
325                            Settings.Secure.putStringForUser(mContext.getContentResolver(),
326                                    getConfig().secureSettingName, allowedItems, userId);
327                        }
328
329                    }
330                }
331            }
332        }
333
334        out.endTag(null, getConfig().xmlTag);
335    }
336
337    protected void migrateToXml() {
338        loadAllowedComponentsFromSettings();
339    }
340
341    public void readXml(XmlPullParser parser, Predicate<String> allowedManagedServicePackages)
342            throws XmlPullParserException, IOException {
343        // upgrade xml
344        int xmlVersion = XmlUtils.readIntAttribute(parser, ATT_VERSION, 0);
345        final List<UserInfo> activeUsers = mUm.getUsers(true);
346        for (UserInfo userInfo : activeUsers) {
347            upgradeXml(xmlVersion, userInfo.getUserHandle().getIdentifier());
348        }
349
350        // read grants
351        int type;
352        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
353            String tag = parser.getName();
354            if (type == XmlPullParser.END_TAG
355                    && getConfig().xmlTag.equals(tag)) {
356                break;
357            }
358            if (type == XmlPullParser.START_TAG) {
359                if (TAG_MANAGED_SERVICES.equals(tag)) {
360                    Slog.i(TAG, "Read " + mConfig.caption + " permissions from xml");
361
362                    final String approved = XmlUtils.readStringAttribute(parser, ATT_APPROVED_LIST);
363                    final int userId = XmlUtils.readIntAttribute(parser, ATT_USER_ID, 0);
364                    final boolean isPrimary =
365                            XmlUtils.readBooleanAttribute(parser, ATT_IS_PRIMARY, true);
366
367                    if (allowedManagedServicePackages == null ||
368                            allowedManagedServicePackages.test(getPackageName(approved))) {
369                        if (mUm.getUserInfo(userId) != null) {
370                            addApprovedList(approved, userId, isPrimary);
371                        }
372                        mUseXml = true;
373                    }
374                }
375            }
376        }
377        rebindServices(false);
378    }
379
380    protected void upgradeXml(final int xmlVersion, final int userId) {}
381
382    private void loadAllowedComponentsFromSettings() {
383        for (UserInfo user : mUm.getUsers()) {
384            final ContentResolver cr = mContext.getContentResolver();
385            addApprovedList(Settings.Secure.getStringForUser(
386                    cr,
387                    getConfig().secureSettingName,
388                    user.id), user.id, true);
389            if (!TextUtils.isEmpty(getConfig().secondarySettingName)) {
390                addApprovedList(Settings.Secure.getStringForUser(
391                        cr,
392                        getConfig().secondarySettingName,
393                        user.id), user.id, false);
394            }
395        }
396        Slog.d(TAG, "Done loading approved values from settings");
397    }
398
399    protected void addApprovedList(String approved, int userId, boolean isPrimary) {
400        if (TextUtils.isEmpty(approved)) {
401            approved = "";
402        }
403        ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.get(userId);
404        if (approvedByType == null) {
405            approvedByType = new ArrayMap<>();
406            mApproved.put(userId, approvedByType);
407        }
408
409        ArraySet<String> approvedList = approvedByType.get(isPrimary);
410        if (approvedList == null) {
411            approvedList = new ArraySet<>();
412            approvedByType.put(isPrimary, approvedList);
413        }
414
415        String[] approvedArray = approved.split(ENABLED_SERVICES_SEPARATOR);
416        for (String pkgOrComponent : approvedArray) {
417            String approvedItem = getApprovedValue(pkgOrComponent);
418            if (approvedItem != null) {
419                approvedList.add(approvedItem);
420            }
421        }
422    }
423
424    protected boolean isComponentEnabledForPackage(String pkg) {
425        return mEnabledServicesPackageNames.contains(pkg);
426    }
427
428    protected void setPackageOrComponentEnabled(String pkgOrComponent, int userId,
429            boolean isPrimary, boolean enabled) {
430        Slog.i(TAG,
431                (enabled ? " Allowing " : "Disallowing ") + mConfig.caption + " " + pkgOrComponent);
432        ArrayMap<Boolean, ArraySet<String>> allowedByType = mApproved.get(userId);
433        if (allowedByType == null) {
434            allowedByType = new ArrayMap<>();
435            mApproved.put(userId, allowedByType);
436        }
437        ArraySet<String> approved = allowedByType.get(isPrimary);
438        if (approved == null) {
439            approved = new ArraySet<>();
440            allowedByType.put(isPrimary, approved);
441        }
442        String approvedItem = getApprovedValue(pkgOrComponent);
443
444        if (approvedItem != null) {
445            if (enabled) {
446                approved.add(approvedItem);
447            } else {
448                approved.remove(approvedItem);
449            }
450        }
451
452        rebindServices(false);
453    }
454
455    private String getApprovedValue(String pkgOrComponent) {
456        if (mApprovalLevel == APPROVAL_BY_COMPONENT) {
457            if(ComponentName.unflattenFromString(pkgOrComponent) != null) {
458                return pkgOrComponent;
459            }
460            return null;
461        } else {
462            return getPackageName(pkgOrComponent);
463        }
464    }
465
466    protected String getApproved(int userId, boolean primary) {
467        final ArrayMap<Boolean, ArraySet<String>> allowedByType =
468                mApproved.getOrDefault(userId, new ArrayMap<>());
469        ArraySet<String> approved = allowedByType.getOrDefault(primary, new ArraySet<>());
470        return String.join(ENABLED_SERVICES_SEPARATOR, approved);
471    }
472
473    protected List<ComponentName> getAllowedComponents(int userId) {
474        final List<ComponentName> allowedComponents = new ArrayList<>();
475        final ArrayMap<Boolean, ArraySet<String>> allowedByType =
476                mApproved.getOrDefault(userId, new ArrayMap<>());
477        for (int i = 0; i < allowedByType.size(); i++) {
478            final ArraySet<String> allowed = allowedByType.valueAt(i);
479            for (int j = 0; j < allowed.size(); j++) {
480                ComponentName cn = ComponentName.unflattenFromString(allowed.valueAt(j));
481                if (cn != null) {
482                    allowedComponents.add(cn);
483                }
484            }
485        }
486        return allowedComponents;
487    }
488
489    protected List<String> getAllowedPackages(int userId) {
490        final List<String> allowedPackages = new ArrayList<>();
491        final ArrayMap<Boolean, ArraySet<String>> allowedByType =
492                mApproved.getOrDefault(userId, new ArrayMap<>());
493        for (int i = 0; i < allowedByType.size(); i++) {
494            final ArraySet<String> allowed = allowedByType.valueAt(i);
495            for (int j = 0; j < allowed.size(); j++) {
496                String pkgName = getPackageName(allowed.valueAt(j));
497                if (!TextUtils.isEmpty(pkgName)) {
498                    allowedPackages.add(pkgName);
499                }
500            }
501        }
502        return allowedPackages;
503    }
504
505    protected boolean isPackageOrComponentAllowed(String pkgOrComponent, int userId) {
506        ArrayMap<Boolean, ArraySet<String>> allowedByType =
507                mApproved.getOrDefault(userId, new ArrayMap<>());
508        for (int i = 0; i < allowedByType.size(); i++) {
509            ArraySet<String> allowed = allowedByType.valueAt(i);
510            if (allowed.contains(pkgOrComponent)) {
511                return true;
512            }
513        }
514        return false;
515    }
516
517    public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uidList) {
518        if (DEBUG) Slog.d(TAG, "onPackagesChanged removingPackage=" + removingPackage
519                + " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList))
520                + " mEnabledServicesPackageNames=" + mEnabledServicesPackageNames);
521
522        if (pkgList != null && (pkgList.length > 0)) {
523            boolean anyServicesInvolved = false;
524            // Remove notification settings for uninstalled package
525            if (removingPackage) {
526                int size = Math.min(pkgList.length, uidList.length);
527                for (int i = 0; i < size; i++) {
528                    final String pkg = pkgList[i];
529                    final int userId = UserHandle.getUserId(uidList[i]);
530                    anyServicesInvolved = removeUninstalledItemsFromApprovedLists(userId, pkg);
531                }
532            }
533            for (String pkgName : pkgList) {
534                if (mEnabledServicesPackageNames.contains(pkgName)) {
535                    anyServicesInvolved = true;
536                }
537            }
538
539            if (anyServicesInvolved) {
540                // make sure we're still bound to any of our services who may have just upgraded
541                rebindServices(false);
542            }
543        }
544    }
545
546    public void onUserRemoved(int user) {
547        Slog.i(TAG, "Removing approved services for removed user " + user);
548        mApproved.remove(user);
549        rebindServices(true);
550    }
551
552    public void onUserSwitched(int user) {
553        if (DEBUG) Slog.d(TAG, "onUserSwitched u=" + user);
554        if (Arrays.equals(mLastSeenProfileIds, mUserProfiles.getCurrentProfileIds())) {
555            if (DEBUG) Slog.d(TAG, "Current profile IDs didn't change, skipping rebindServices().");
556            return;
557        }
558        rebindServices(true);
559    }
560
561    public void onUserUnlocked(int user) {
562        if (DEBUG) Slog.d(TAG, "onUserUnlocked u=" + user);
563        rebindServices(false);
564    }
565
566    private ManagedServiceInfo getServiceFromTokenLocked(IInterface service) {
567        if (service == null) {
568            return null;
569        }
570        final IBinder token = service.asBinder();
571        final int N = mServices.size();
572        for (int i = 0; i < N; i++) {
573            final ManagedServiceInfo info = mServices.get(i);
574            if (info.service.asBinder() == token) return info;
575        }
576        return null;
577    }
578
579    protected boolean isServiceTokenValidLocked(IInterface service) {
580        if (service == null) {
581            return false;
582        }
583        ManagedServiceInfo info = getServiceFromTokenLocked(service);
584        if (info != null) {
585            return true;
586        }
587        return false;
588    }
589
590    protected ManagedServiceInfo checkServiceTokenLocked(IInterface service) {
591        checkNotNull(service);
592        ManagedServiceInfo info = getServiceFromTokenLocked(service);
593        if (info != null) {
594            return info;
595        }
596        throw new SecurityException("Disallowed call from unknown " + getCaption() + ": "
597                + service + " " + service.getClass());
598    }
599
600    public void unregisterService(IInterface service, int userid) {
601        checkNotNull(service);
602        // no need to check permissions; if your service binder is in the list,
603        // that's proof that you had permission to add it in the first place
604        unregisterServiceImpl(service, userid);
605    }
606
607    public void registerService(IInterface service, ComponentName component, int userid) {
608        checkNotNull(service);
609        ManagedServiceInfo info = registerServiceImpl(service, component, userid);
610        if (info != null) {
611            onServiceAdded(info);
612        }
613    }
614
615    /**
616     * Add a service to our callbacks. The lifecycle of this service is managed externally,
617     * but unlike a system service, it should not be considered privileged.
618     * */
619    protected void registerGuestService(ManagedServiceInfo guest) {
620        checkNotNull(guest.service);
621        if (!checkType(guest.service)) {
622            throw new IllegalArgumentException();
623        }
624        if (registerServiceImpl(guest) != null) {
625            onServiceAdded(guest);
626        }
627    }
628
629    protected void setComponentState(ComponentName component, boolean enabled) {
630        boolean previous = !mSnoozingForCurrentProfiles.contains(component);
631        if (previous == enabled) {
632            return;
633        }
634
635        if (enabled) {
636            mSnoozingForCurrentProfiles.remove(component);
637        } else {
638            mSnoozingForCurrentProfiles.add(component);
639        }
640
641        // State changed
642        Slog.d(TAG, ((enabled) ? "Enabling " : "Disabling ") + "component " +
643                component.flattenToShortString());
644
645        synchronized (mMutex) {
646            final int[] userIds = mUserProfiles.getCurrentProfileIds();
647
648            for (int userId : userIds) {
649                if (enabled) {
650                    registerServiceLocked(component, userId);
651                } else {
652                    unregisterServiceLocked(component, userId);
653                }
654            }
655        }
656    }
657
658    private @NonNull ArraySet<ComponentName> loadComponentNamesFromValues(
659            ArraySet<String> approved, int userId) {
660        if (approved == null || approved.size() == 0)
661            return new ArraySet<>();
662        ArraySet<ComponentName> result = new ArraySet<>(approved.size());
663        for (int i = 0; i < approved.size(); i++) {
664            final String packageOrComponent = approved.valueAt(i);
665            if (!TextUtils.isEmpty(packageOrComponent)) {
666                ComponentName component = ComponentName.unflattenFromString(packageOrComponent);
667                if (component != null) {
668                    result.add(component);
669                } else {
670                    result.addAll(queryPackageForServices(packageOrComponent, userId));
671                }
672            }
673        }
674        return result;
675    }
676
677    protected Set<ComponentName> queryPackageForServices(String packageName, int userId) {
678        return queryPackageForServices(packageName, 0, userId);
679    }
680
681    protected Set<ComponentName> queryPackageForServices(String packageName, int extraFlags,
682            int userId) {
683        Set<ComponentName> installed = new ArraySet<>();
684        final PackageManager pm = mContext.getPackageManager();
685        Intent queryIntent = new Intent(mConfig.serviceInterface);
686        if (!TextUtils.isEmpty(packageName)) {
687            queryIntent.setPackage(packageName);
688        }
689        List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
690                queryIntent,
691                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA | extraFlags,
692                userId);
693        if (DEBUG)
694            Slog.v(TAG, mConfig.serviceInterface + " services: " + installedServices);
695        if (installedServices != null) {
696            for (int i = 0, count = installedServices.size(); i < count; i++) {
697                ResolveInfo resolveInfo = installedServices.get(i);
698                ServiceInfo info = resolveInfo.serviceInfo;
699
700                ComponentName component = new ComponentName(info.packageName, info.name);
701                if (!mConfig.bindPermission.equals(info.permission)) {
702                    Slog.w(TAG, "Skipping " + getCaption() + " service "
703                        + info.packageName + "/" + info.name
704                        + ": it does not require the permission "
705                        + mConfig.bindPermission);
706                    continue;
707                }
708                installed.add(component);
709            }
710        }
711        return installed;
712    }
713
714    private void trimApprovedListsAccordingToInstalledServices() {
715        int N = mApproved.size();
716        for (int i = 0 ; i < N; i++) {
717            final int userId = mApproved.keyAt(i);
718            final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.valueAt(i);
719            int M = approvedByType.size();
720            for (int j = 0; j < M; j++) {
721                final ArraySet<String> approved = approvedByType.valueAt(j);
722                int P = approved.size();
723                for (int k = P - 1; k >= 0; k--) {
724                    final String approvedPackageOrComponent = approved.valueAt(k);
725                    if (!isValidEntry(approvedPackageOrComponent, userId)){
726                        approved.removeAt(k);
727                        Slog.v(TAG, "Removing " + approvedPackageOrComponent
728                                + " from approved list; no matching services found");
729                    } else {
730                        if (DEBUG) {
731                            Slog.v(TAG, "Keeping " + approvedPackageOrComponent
732                                    + " on approved list; matching services found");
733                        }
734                    }
735                }
736            }
737        }
738    }
739
740    private boolean removeUninstalledItemsFromApprovedLists(int uninstalledUserId, String pkg) {
741        boolean removed = false;
742        final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.get(uninstalledUserId);
743        if (approvedByType != null) {
744            int M = approvedByType.size();
745            for (int j = 0; j < M; j++) {
746                final ArraySet<String> approved = approvedByType.valueAt(j);
747                int O = approved.size();
748                for (int k = O - 1; k >= 0; k--) {
749                    final String packageOrComponent = approved.valueAt(k);
750                    final String packageName = getPackageName(packageOrComponent);
751                    if (TextUtils.equals(pkg, packageName)) {
752                        approved.removeAt(k);
753                        if (DEBUG) {
754                            Slog.v(TAG, "Removing " + packageOrComponent
755                                    + " from approved list; uninstalled");
756                        }
757                    }
758                }
759            }
760        }
761        return removed;
762    }
763
764    protected String getPackageName(String packageOrComponent) {
765        final ComponentName component = ComponentName.unflattenFromString(packageOrComponent);
766        if (component != null) {
767            return component.getPackageName();
768        } else {
769            return packageOrComponent;
770        }
771    }
772
773    protected boolean isValidEntry(String packageOrComponent, int userId) {
774        return hasMatchingServices(packageOrComponent, userId);
775    }
776
777    private boolean hasMatchingServices(String packageOrComponent, int userId) {
778        if (!TextUtils.isEmpty(packageOrComponent)) {
779            final String packageName = getPackageName(packageOrComponent);
780            return queryPackageForServices(packageName, userId).size() > 0;
781        }
782        return false;
783    }
784
785    /**
786     * Called whenever packages change, the user switches, or the secure setting
787     * is altered. (For example in response to USER_SWITCHED in our broadcast receiver)
788     */
789    protected void rebindServices(boolean forceRebind) {
790        if (DEBUG) Slog.d(TAG, "rebindServices");
791        final int[] userIds = mUserProfiles.getCurrentProfileIds();
792        final int nUserIds = userIds.length;
793
794        final SparseArray<ArraySet<ComponentName>> componentsByUser = new SparseArray<>();
795
796        for (int i = 0; i < nUserIds; ++i) {
797            final int userId = userIds[i];
798            final ArrayMap<Boolean, ArraySet<String>> approvedLists = mApproved.get(userIds[i]);
799            if (approvedLists != null) {
800                final int N = approvedLists.size();
801                for (int j = 0; j < N; j++) {
802                    ArraySet<ComponentName> approvedByUser = componentsByUser.get(userId);
803                    if (approvedByUser == null) {
804                        approvedByUser = new ArraySet<>();
805                        componentsByUser.put(userId, approvedByUser);
806                    }
807                    approvedByUser.addAll(
808                            loadComponentNamesFromValues(approvedLists.valueAt(j), userId));
809                }
810            }
811        }
812
813        final ArrayList<ManagedServiceInfo> removableBoundServices = new ArrayList<>();
814        final SparseArray<Set<ComponentName>> toAdd = new SparseArray<>();
815
816        synchronized (mMutex) {
817            // Rebind to non-system services if user switched
818            for (ManagedServiceInfo service : mServices) {
819                if (!service.isSystem && !service.isGuest(this)) {
820                    removableBoundServices.add(service);
821                }
822            }
823
824            mEnabledServicesForCurrentProfiles.clear();
825            mEnabledServicesPackageNames.clear();
826
827            for (int i = 0; i < nUserIds; ++i) {
828                // decode the list of components
829                final ArraySet<ComponentName> userComponents = componentsByUser.get(userIds[i]);
830                if (null == userComponents) {
831                    toAdd.put(userIds[i], new ArraySet<>());
832                    continue;
833                }
834
835                final Set<ComponentName> add = new HashSet<>(userComponents);
836                add.removeAll(mSnoozingForCurrentProfiles);
837
838                toAdd.put(userIds[i], add);
839
840                mEnabledServicesForCurrentProfiles.addAll(userComponents);
841
842                for (int j = 0; j < userComponents.size(); j++) {
843                    final ComponentName component = userComponents.valueAt(j);
844                    mEnabledServicesPackageNames.add(component.getPackageName());
845                }
846            }
847        }
848
849        for (ManagedServiceInfo info : removableBoundServices) {
850            final ComponentName component = info.component;
851            final int oldUser = info.userid;
852            final Set<ComponentName> allowedComponents = toAdd.get(info.userid);
853            if (allowedComponents != null) {
854                if (allowedComponents.contains(component) && !forceRebind) {
855                    // Already bound, don't need to bind again.
856                    allowedComponents.remove(component);
857                } else {
858                    // No longer allowed to be bound, or must rebind.
859                    Slog.v(TAG, "disabling " + getCaption() + " for user "
860                            + oldUser + ": " + component);
861                    unregisterService(component, oldUser);
862                }
863            }
864        }
865
866        for (int i = 0; i < nUserIds; ++i) {
867            final Set<ComponentName> add = toAdd.get(userIds[i]);
868            for (ComponentName component : add) {
869                try {
870                    ServiceInfo info = mPm.getServiceInfo(component,
871                            PackageManager.MATCH_DIRECT_BOOT_AWARE
872                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userIds[i]);
873                    if (info == null) {
874                        Slog.w(TAG, "Not binding " + getCaption() + " service " + component
875                                + ": service not found");
876                        continue;
877                    }
878                    if (!mConfig.bindPermission.equals(info.permission)) {
879                        Slog.w(TAG, "Not binding " + getCaption() + " service " + component
880                                + ": it does not require the permission " + mConfig.bindPermission);
881                        continue;
882                    }
883                    Slog.v(TAG,
884                            "enabling " + getCaption() + " for " + userIds[i] + ": " + component);
885                    registerService(component, userIds[i]);
886                } catch (RemoteException e) {
887                    e.rethrowFromSystemServer();
888                }
889            }
890        }
891
892        mLastSeenProfileIds = userIds;
893    }
894
895    /**
896     * Version of registerService that takes the name of a service component to bind to.
897     */
898    private void registerService(final ComponentName name, final int userid) {
899        synchronized (mMutex) {
900            registerServiceLocked(name, userid);
901        }
902    }
903
904    /**
905     * Inject a system service into the management list.
906     */
907    public void registerSystemService(final ComponentName name, final int userid) {
908        synchronized (mMutex) {
909            registerServiceLocked(name, userid, true /* isSystem */);
910        }
911    }
912
913    private void registerServiceLocked(final ComponentName name, final int userid) {
914        registerServiceLocked(name, userid, false /* isSystem */);
915    }
916
917    private void registerServiceLocked(final ComponentName name, final int userid,
918            final boolean isSystem) {
919        if (DEBUG) Slog.v(TAG, "registerService: " + name + " u=" + userid);
920
921        final String servicesBindingTag = name.toString() + "/" + userid;
922        if (mServicesBinding.contains(servicesBindingTag)) {
923            Slog.v(TAG, "Not registering " + name + " as bind is already in progress");
924            // stop registering this thing already! we're working on it
925            return;
926        }
927        mServicesBinding.add(servicesBindingTag);
928
929        final int N = mServices.size();
930        for (int i = N - 1; i >= 0; i--) {
931            final ManagedServiceInfo info = mServices.get(i);
932            if (name.equals(info.component)
933                && info.userid == userid) {
934                // cut old connections
935                Slog.v(TAG, "    disconnecting old " + getCaption() + ": " + info.service);
936                removeServiceLocked(i);
937                if (info.connection != null) {
938                    try {
939                        mContext.unbindService(info.connection);
940                    } catch (IllegalArgumentException e) {
941                        Slog.e(TAG, "failed to unbind " + name, e);
942                    }
943                }
944            }
945        }
946
947        Intent intent = new Intent(mConfig.serviceInterface);
948        intent.setComponent(name);
949
950        intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mConfig.clientLabel);
951
952        final PendingIntent pendingIntent = PendingIntent.getActivity(
953            mContext, 0, new Intent(mConfig.settingsAction), 0);
954        intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
955
956        ApplicationInfo appInfo = null;
957        try {
958            appInfo = mContext.getPackageManager().getApplicationInfo(
959                name.getPackageName(), 0);
960        } catch (NameNotFoundException e) {
961            // Ignore if the package doesn't exist we won't be able to bind to the service.
962        }
963        final int targetSdkVersion =
964            appInfo != null ? appInfo.targetSdkVersion : Build.VERSION_CODES.BASE;
965
966        try {
967            Slog.v(TAG, "binding: " + intent);
968            ServiceConnection serviceConnection = new ServiceConnection() {
969                IInterface mService;
970
971                @Override
972                public void onServiceConnected(ComponentName name, IBinder binder) {
973                    boolean added = false;
974                    ManagedServiceInfo info = null;
975                    synchronized (mMutex) {
976                        mServicesRebinding.remove(servicesBindingTag);
977                        mServicesBinding.remove(servicesBindingTag);
978                        try {
979                            mService = asInterface(binder);
980                            info = newServiceInfo(mService, name,
981                                userid, isSystem, this, targetSdkVersion);
982                            binder.linkToDeath(info, 0);
983                            added = mServices.add(info);
984                        } catch (RemoteException e) {
985                            // already dead
986                        }
987                    }
988                    if (added) {
989                        onServiceAdded(info);
990                    }
991                }
992
993                @Override
994                public void onServiceDisconnected(ComponentName name) {
995                    mServicesBinding.remove(servicesBindingTag);
996                    Slog.v(TAG, getCaption() + " connection lost: " + name);
997                }
998
999                @Override
1000                public void onBindingDied(ComponentName name) {
1001                    Slog.w(TAG, getCaption() + " binding died: " + name);
1002                    synchronized (mMutex) {
1003                        mServicesBinding.remove(servicesBindingTag);
1004                        try {
1005                            mContext.unbindService(this);
1006                        } catch (IllegalArgumentException e) {
1007                            Slog.e(TAG, "failed to unbind " + name, e);
1008                        }
1009                        if (!mServicesRebinding.contains(servicesBindingTag)) {
1010                            mServicesRebinding.add(servicesBindingTag);
1011                            mHandler.postDelayed(new Runnable() {
1012                                    @Override
1013                                    public void run() {
1014                                        registerService(name, userid);
1015                                    }
1016                               }, ON_BINDING_DIED_REBIND_DELAY_MS);
1017                        } else {
1018                            Slog.v(TAG, getCaption() + " not rebinding as "
1019                                    + "a previous rebind attempt was made: " + name);
1020                        }
1021                    }
1022                }
1023            };
1024            if (!mContext.bindServiceAsUser(intent,
1025                serviceConnection,
1026                BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE | BIND_ALLOW_WHITELIST_MANAGEMENT,
1027                new UserHandle(userid))) {
1028                mServicesBinding.remove(servicesBindingTag);
1029                Slog.w(TAG, "Unable to bind " + getCaption() + " service: " + intent);
1030                return;
1031            }
1032        } catch (SecurityException ex) {
1033            mServicesBinding.remove(servicesBindingTag);
1034            Slog.e(TAG, "Unable to bind " + getCaption() + " service: " + intent, ex);
1035        }
1036    }
1037
1038    /**
1039     * Remove a service for the given user by ComponentName
1040     */
1041    private void unregisterService(ComponentName name, int userid) {
1042        synchronized (mMutex) {
1043            unregisterServiceLocked(name, userid);
1044        }
1045    }
1046
1047    private void unregisterServiceLocked(ComponentName name, int userid) {
1048        final int N = mServices.size();
1049        for (int i = N - 1; i >= 0; i--) {
1050            final ManagedServiceInfo info = mServices.get(i);
1051            if (name.equals(info.component) && info.userid == userid) {
1052                removeServiceLocked(i);
1053                if (info.connection != null) {
1054                    try {
1055                        mContext.unbindService(info.connection);
1056                    } catch (IllegalArgumentException ex) {
1057                        // something happened to the service: we think we have a connection
1058                        // but it's bogus.
1059                        Slog.e(TAG, getCaption() + " " + name + " could not be unbound: " + ex);
1060                    }
1061                }
1062            }
1063        }
1064    }
1065
1066    /**
1067     * Removes a service from the list but does not unbind
1068     *
1069     * @return the removed service.
1070     */
1071    private ManagedServiceInfo removeServiceImpl(IInterface service, final int userid) {
1072        if (DEBUG) Slog.d(TAG, "removeServiceImpl service=" + service + " u=" + userid);
1073        ManagedServiceInfo serviceInfo = null;
1074        synchronized (mMutex) {
1075            final int N = mServices.size();
1076            for (int i = N - 1; i >= 0; i--) {
1077                final ManagedServiceInfo info = mServices.get(i);
1078                if (info.service.asBinder() == service.asBinder() && info.userid == userid) {
1079                    Slog.d(TAG, "Removing active service " + info.component);
1080                    serviceInfo = removeServiceLocked(i);
1081                }
1082            }
1083        }
1084        return serviceInfo;
1085    }
1086
1087    private ManagedServiceInfo removeServiceLocked(int i) {
1088        final ManagedServiceInfo info = mServices.remove(i);
1089        onServiceRemovedLocked(info);
1090        return info;
1091    }
1092
1093    private void checkNotNull(IInterface service) {
1094        if (service == null) {
1095            throw new IllegalArgumentException(getCaption() + " must not be null");
1096        }
1097    }
1098
1099    private ManagedServiceInfo registerServiceImpl(final IInterface service,
1100            final ComponentName component, final int userid) {
1101        ManagedServiceInfo info = newServiceInfo(service, component, userid,
1102                true /*isSystem*/, null /*connection*/, Build.VERSION_CODES.LOLLIPOP);
1103        return registerServiceImpl(info);
1104    }
1105
1106    private ManagedServiceInfo registerServiceImpl(ManagedServiceInfo info) {
1107        synchronized (mMutex) {
1108            try {
1109                info.service.asBinder().linkToDeath(info, 0);
1110                mServices.add(info);
1111                return info;
1112            } catch (RemoteException e) {
1113                // already dead
1114            }
1115        }
1116        return null;
1117    }
1118
1119    /**
1120     * Removes a service from the list and unbinds.
1121     */
1122    private void unregisterServiceImpl(IInterface service, int userid) {
1123        ManagedServiceInfo info = removeServiceImpl(service, userid);
1124        if (info != null && info.connection != null && !info.isGuest(this)) {
1125            mContext.unbindService(info.connection);
1126        }
1127    }
1128
1129    public class ManagedServiceInfo implements IBinder.DeathRecipient {
1130        public IInterface service;
1131        public ComponentName component;
1132        public int userid;
1133        public boolean isSystem;
1134        public ServiceConnection connection;
1135        public int targetSdkVersion;
1136
1137        public ManagedServiceInfo(IInterface service, ComponentName component,
1138                int userid, boolean isSystem, ServiceConnection connection, int targetSdkVersion) {
1139            this.service = service;
1140            this.component = component;
1141            this.userid = userid;
1142            this.isSystem = isSystem;
1143            this.connection = connection;
1144            this.targetSdkVersion = targetSdkVersion;
1145        }
1146
1147        public boolean isGuest(ManagedServices host) {
1148            return ManagedServices.this != host;
1149        }
1150
1151        public ManagedServices getOwner() {
1152            return ManagedServices.this;
1153        }
1154
1155        @Override
1156        public String toString() {
1157            return new StringBuilder("ManagedServiceInfo[")
1158                    .append("component=").append(component)
1159                    .append(",userid=").append(userid)
1160                    .append(",isSystem=").append(isSystem)
1161                    .append(",targetSdkVersion=").append(targetSdkVersion)
1162                    .append(",connection=").append(connection == null ? null : "<connection>")
1163                    .append(",service=").append(service)
1164                    .append(']').toString();
1165        }
1166
1167        public void writeToProto(ProtoOutputStream proto, long fieldId, ManagedServices host) {
1168            final long token = proto.start(fieldId);
1169            component.writeToProto(proto, ManagedServiceInfoProto.COMPONENT);
1170            proto.write(ManagedServiceInfoProto.USER_ID, userid);
1171            proto.write(ManagedServiceInfoProto.SERVICE, service.getClass().getName());
1172            proto.write(ManagedServiceInfoProto.IS_SYSTEM, isSystem);
1173            proto.write(ManagedServiceInfoProto.IS_GUEST, isGuest(host));
1174            proto.end(token);
1175        }
1176
1177        public boolean enabledAndUserMatches(int nid) {
1178            if (!isEnabledForCurrentProfiles()) {
1179                return false;
1180            }
1181            if (this.userid == UserHandle.USER_ALL) return true;
1182            if (this.isSystem) return true;
1183            if (nid == UserHandle.USER_ALL || nid == this.userid) return true;
1184            return supportsProfiles()
1185                    && mUserProfiles.isCurrentProfile(nid)
1186                    && isPermittedForProfile(nid);
1187        }
1188
1189        public boolean supportsProfiles() {
1190            return targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP;
1191        }
1192
1193        @Override
1194        public void binderDied() {
1195            if (DEBUG) Slog.d(TAG, "binderDied");
1196            // Remove the service, but don't unbind from the service. The system will bring the
1197            // service back up, and the onServiceConnected handler will read the service with the
1198            // new binding. If this isn't a bound service, and is just a registered
1199            // service, just removing it from the list is all we need to do anyway.
1200            removeServiceImpl(this.service, this.userid);
1201        }
1202
1203        /** convenience method for looking in mEnabledServicesForCurrentProfiles */
1204        public boolean isEnabledForCurrentProfiles() {
1205            if (this.isSystem) return true;
1206            if (this.connection == null) return false;
1207            return mEnabledServicesForCurrentProfiles.contains(this.component);
1208        }
1209
1210        /**
1211         * Returns true if this service is allowed to receive events for the given userId. A
1212         * managed profile owner can disallow non-system services running outside of the profile
1213         * from receiving events from the profile.
1214         */
1215        public boolean isPermittedForProfile(int userId) {
1216            if (!mUserProfiles.isManagedProfile(userId)) {
1217                return true;
1218            }
1219            DevicePolicyManager dpm =
1220                    (DevicePolicyManager) mContext.getSystemService(DEVICE_POLICY_SERVICE);
1221            final long identity = Binder.clearCallingIdentity();
1222            try {
1223                return dpm.isNotificationListenerServicePermitted(
1224                        component.getPackageName(), userId);
1225            } finally {
1226                Binder.restoreCallingIdentity(identity);
1227            }
1228        }
1229    }
1230
1231    /** convenience method for looking in mEnabledServicesForCurrentProfiles */
1232    public boolean isComponentEnabledForCurrentProfiles(ComponentName component) {
1233        return mEnabledServicesForCurrentProfiles.contains(component);
1234    }
1235
1236    public static class UserProfiles {
1237        // Profiles of the current user.
1238        private final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();
1239
1240        public void updateCache(@NonNull Context context) {
1241            UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
1242            if (userManager != null) {
1243                int currentUserId = ActivityManager.getCurrentUser();
1244                List<UserInfo> profiles = userManager.getProfiles(currentUserId);
1245                synchronized (mCurrentProfiles) {
1246                    mCurrentProfiles.clear();
1247                    for (UserInfo user : profiles) {
1248                        mCurrentProfiles.put(user.id, user);
1249                    }
1250                }
1251            }
1252        }
1253
1254        public int[] getCurrentProfileIds() {
1255            synchronized (mCurrentProfiles) {
1256                int[] users = new int[mCurrentProfiles.size()];
1257                final int N = mCurrentProfiles.size();
1258                for (int i = 0; i < N; ++i) {
1259                    users[i] = mCurrentProfiles.keyAt(i);
1260                }
1261                return users;
1262            }
1263        }
1264
1265        public boolean isCurrentProfile(int userId) {
1266            synchronized (mCurrentProfiles) {
1267                return mCurrentProfiles.get(userId) != null;
1268            }
1269        }
1270
1271        public boolean isManagedProfile(int userId) {
1272            synchronized (mCurrentProfiles) {
1273                UserInfo user = mCurrentProfiles.get(userId);
1274                return user != null && user.isManagedProfile();
1275            }
1276        }
1277    }
1278
1279    public static class Config {
1280        public String caption;
1281        public String serviceInterface;
1282        public String secureSettingName;
1283        public String secondarySettingName;
1284        public String xmlTag;
1285        public String bindPermission;
1286        public String settingsAction;
1287        public int clientLabel;
1288    }
1289}
1290