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