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