1 /*
2  * Copyright (C) 2022 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.server.companion;
18 
19 import static android.content.Context.BIND_ALMOST_PERCEPTIBLE;
20 import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE;
21 import static android.os.Process.THREAD_PRIORITY_DEFAULT;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.SuppressLint;
26 import android.annotation.UserIdInt;
27 import android.companion.AssociationInfo;
28 import android.companion.CompanionDeviceService;
29 import android.companion.ICompanionDeviceService;
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.os.Handler;
34 import android.os.IBinder;
35 import android.util.Log;
36 
37 import com.android.internal.infra.ServiceConnector;
38 import com.android.server.ServiceThread;
39 
40 /**
41  * Manages a connection (binding) to an instance of {@link CompanionDeviceService} running in the
42  * application process.
43  */
44 @SuppressLint("LongLogTag")
45 class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDeviceService> {
46     private static final String TAG = "CDM_CompanionServiceConnector";
47     private static final boolean DEBUG = false;
48 
49     /* Unbinding before executing the callbacks can cause problems. Wait 5-seconds before unbind. */
50     private static final long UNBIND_POST_DELAY_MS = 5_000;
51 
52     /** Listener for changes to the state of the {@link CompanionDeviceServiceConnector}  */
53     interface Listener {
onBindingDied(@serIdInt int userId, @NonNull String packageName, @NonNull CompanionDeviceServiceConnector serviceConnector)54         void onBindingDied(@UserIdInt int userId, @NonNull String packageName,
55                 @NonNull CompanionDeviceServiceConnector serviceConnector);
56     }
57 
58     private final @UserIdInt int mUserId;
59     private final @NonNull ComponentName mComponentName;
60     // IMPORTANT: this can (and will!) be null (at the moment, CompanionApplicationController only
61     // installs a listener to the primary ServiceConnector), hence we should always null-check the
62     // reference before calling on it.
63     private @Nullable Listener mListener;
64     private boolean mIsPrimary;
65 
66     /**
67      * Create a CompanionDeviceServiceConnector instance.
68      *
69      * For self-managed apps, the binding flag will be BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE
70      * (oom_score_adj = VISIBLE_APP_ADJ = 100).
71      *
72      * For non self-managed apps, the binding flag will be BIND_ALMOST_PERCEPTIBLE
73      * (oom_score_adj = PERCEPTIBLE_MEDIUM_APP = 225). The target service will be treated
74      * as important as a perceptible app (IMPORTANCE_VISIBLE = 200), and will be unbound when
75      * the app is removed from task manager.
76      *
77      * One time permission's importance level to keep session alive is
78      * IMPORTANCE_FOREGROUND_SERVICE = 125. In order to kill the one time permission session, the
79      * service importance level should be higher than 125.
80      */
newInstance(@onNull Context context, @UserIdInt int userId, @NonNull ComponentName componentName, boolean isSelfManaged, boolean isPrimary)81     static CompanionDeviceServiceConnector newInstance(@NonNull Context context,
82             @UserIdInt int userId, @NonNull ComponentName componentName, boolean isSelfManaged,
83             boolean isPrimary) {
84         final int bindingFlags = isSelfManaged ? BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE
85                 : BIND_ALMOST_PERCEPTIBLE;
86         return new CompanionDeviceServiceConnector(
87                 context, userId, componentName, bindingFlags, isPrimary);
88     }
89 
CompanionDeviceServiceConnector(@onNull Context context, @UserIdInt int userId, @NonNull ComponentName componentName, int bindingFlags, boolean isPrimary)90     private CompanionDeviceServiceConnector(@NonNull Context context, @UserIdInt int userId,
91             @NonNull ComponentName componentName, int bindingFlags, boolean isPrimary) {
92         super(context, buildIntent(componentName), bindingFlags, userId, null);
93         mUserId = userId;
94         mComponentName = componentName;
95         mIsPrimary = isPrimary;
96     }
97 
setListener(@ullable Listener listener)98     void setListener(@Nullable Listener listener) {
99         mListener = listener;
100     }
101 
postOnDeviceAppeared(@onNull AssociationInfo associationInfo)102     void postOnDeviceAppeared(@NonNull AssociationInfo associationInfo) {
103         post(companionService -> companionService.onDeviceAppeared(associationInfo));
104     }
105 
postOnDeviceDisappeared(@onNull AssociationInfo associationInfo)106     void postOnDeviceDisappeared(@NonNull AssociationInfo associationInfo) {
107         post(companionService -> companionService.onDeviceDisappeared(associationInfo));
108     }
109 
110     /**
111      * Post "unbind" job, which will run *after* all previously posted jobs complete.
112      *
113      * IMPORTANT: use this method instead of invoking {@link ServiceConnector#unbind()} directly,
114      * because the latter may cause previously posted callback, such as
115      * {@link ICompanionDeviceService#onDeviceDisappeared(AssociationInfo)} to be dropped.
116      *
117      * {@link ICompanionDeviceService} is a non-blocking interface and doesn't wait for job
118      * completion, which makes {@link ServiceConnector#post(VoidJob)} obsolete for ensuring the
119      * order of execution. Give 5 seconds for all the callbacks to finish before unbinding. They
120      * may or may not have finished executing, but we shouldn't let user-overridden methods block
121      * the service from unbinding indefinitely.
122      */
postUnbind()123     void postUnbind() {
124         getJobHandler().postDelayed(this::unbind, UNBIND_POST_DELAY_MS);
125     }
126 
isPrimary()127     boolean isPrimary() {
128         return mIsPrimary;
129     }
130 
getComponentName()131     ComponentName getComponentName() {
132         return mComponentName;
133     }
134 
135     @Override
onServiceConnectionStatusChanged( @onNull ICompanionDeviceService service, boolean isConnected)136     protected void onServiceConnectionStatusChanged(
137             @NonNull ICompanionDeviceService service, boolean isConnected) {
138         if (DEBUG) {
139             Log.d(TAG, "onServiceConnection_StatusChanged() " + mComponentName.toShortString()
140                     + " connected=" + isConnected);
141         }
142     }
143 
144     @Override
binderDied()145     public void binderDied() {
146         super.binderDied();
147 
148         if (DEBUG) Log.d(TAG, "binderDied() " + mComponentName.toShortString());
149 
150         // Handle primary process being killed
151         if (mListener != null) {
152             mListener.onBindingDied(mUserId, mComponentName.getPackageName(), this);
153         }
154     }
155 
156     @Override
binderAsInterface(@onNull IBinder service)157     protected ICompanionDeviceService binderAsInterface(@NonNull IBinder service) {
158         return ICompanionDeviceService.Stub.asInterface(service);
159     }
160 
161     /**
162      * Overrides {@link ServiceConnector.Impl#getJobHandler()} to provide an alternative Thread
163      * ("in form of" a {@link Handler}) to process jobs on.
164      * <p>
165      * (By default, {@link ServiceConnector.Impl} process jobs on the
166      * {@link android.os.Looper#getMainLooper() MainThread} which is a shared singleton thread
167      * within system_server and thus tends to get heavily congested)
168      */
169     @Override
getJobHandler()170     protected @NonNull Handler getJobHandler() {
171         return getServiceThread().getThreadHandler();
172     }
173 
174     @Override
getAutoDisconnectTimeoutMs()175     protected long getAutoDisconnectTimeoutMs() {
176         // Do NOT auto-disconnect.
177         return -1;
178     }
179 
buildIntent(@onNull ComponentName componentName)180     private static @NonNull Intent buildIntent(@NonNull ComponentName componentName) {
181         return new Intent(CompanionDeviceService.SERVICE_INTERFACE)
182                 .setComponent(componentName);
183     }
184 
getServiceThread()185     private static @NonNull ServiceThread getServiceThread() {
186         if (sServiceThread == null) {
187             synchronized (CompanionDeviceManagerService.class) {
188                 if (sServiceThread == null) {
189                     sServiceThread = new ServiceThread("companion-device-service-connector",
190                             THREAD_PRIORITY_DEFAULT, /* allowIo */ false);
191                     sServiceThread.start();
192                 }
193             }
194         }
195         return sServiceThread;
196     }
197 
198     /**
199      * A worker thread for the {@link ServiceConnector} to process jobs on.
200      *
201      * <p>
202      *  Do NOT reference directly, use {@link #getServiceThread()} method instead.
203      */
204     private static volatile @Nullable ServiceThread sServiceThread;
205 }
206