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