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 = "CompanionDevice_ServiceConnector"; 47 private static final boolean DEBUG = false; 48 49 /** Listener for changes to the state of the {@link CompanionDeviceServiceConnector} */ 50 interface Listener { onBindingDied(@serIdInt int userId, @NonNull String packageName)51 void onBindingDied(@UserIdInt int userId, @NonNull String packageName); 52 } 53 54 private final @UserIdInt int mUserId; 55 private final @NonNull ComponentName mComponentName; 56 // IMPORTANT: this can (and will!) be null (at the moment, CompanionApplicationController only 57 // installs a listener to the primary ServiceConnector), hence we should always null-check the 58 // reference before calling on it. 59 private @Nullable Listener mListener; 60 61 /** 62 * Create a CompanionDeviceServiceConnector instance. 63 * 64 * For self-managed apps, the binding flag will be BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE 65 * (oom_score_adj = VISIBLE_APP_ADJ = 100). 66 * 67 * For non self-managed apps, the binding flag will be BIND_ALMOST_PERCEPTIBLE 68 * (oom_score_adj = PERCEPTIBLE_MEDIUM_APP = 225). The target service will be treated 69 * as important as a perceptible app (IMPORTANCE_VISIBLE = 200), and will be unbound when 70 * the app is removed from task manager. 71 * 72 * One time permission's importance level to keep session alive is 73 * IMPORTANCE_FOREGROUND_SERVICE = 125. In order to kill the one time permission session, the 74 * service importance level should be higher than 125. 75 */ newInstance(@onNull Context context, @UserIdInt int userId, @NonNull ComponentName componentName, boolean isSelfManaged)76 static CompanionDeviceServiceConnector newInstance(@NonNull Context context, 77 @UserIdInt int userId, @NonNull ComponentName componentName, boolean isSelfManaged) { 78 final int bindingFlags = isSelfManaged ? BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE 79 : BIND_ALMOST_PERCEPTIBLE; 80 return new CompanionDeviceServiceConnector(context, userId, componentName, bindingFlags); 81 } 82 CompanionDeviceServiceConnector(@onNull Context context, @UserIdInt int userId, @NonNull ComponentName componentName, int bindingFlags)83 private CompanionDeviceServiceConnector(@NonNull Context context, @UserIdInt int userId, 84 @NonNull ComponentName componentName, int bindingFlags) { 85 super(context, buildIntent(componentName), bindingFlags, userId, null); 86 mUserId = userId; 87 mComponentName = componentName; 88 } 89 setListener(@ullable Listener listener)90 void setListener(@Nullable Listener listener) { 91 mListener = listener; 92 } 93 postOnDeviceAppeared(@onNull AssociationInfo associationInfo)94 void postOnDeviceAppeared(@NonNull AssociationInfo associationInfo) { 95 post(companionService -> companionService.onDeviceAppeared(associationInfo)); 96 } 97 postOnDeviceDisappeared(@onNull AssociationInfo associationInfo)98 void postOnDeviceDisappeared(@NonNull AssociationInfo associationInfo) { 99 post(companionService -> companionService.onDeviceDisappeared(associationInfo)); 100 } 101 102 /** 103 * Post "unbind" job, which will run *after* all previously posted jobs complete. 104 * 105 * IMPORTANT: use this method instead of invoking {@link ServiceConnector#unbind()} directly, 106 * because the latter may cause previously posted callback, such as 107 * {@link ICompanionDeviceService#onDeviceDisappeared(AssociationInfo)} to be dropped. 108 */ postUnbind()109 void postUnbind() { 110 post(it -> unbind()); 111 } 112 113 @Override onServiceConnectionStatusChanged( @onNull ICompanionDeviceService service, boolean isConnected)114 protected void onServiceConnectionStatusChanged( 115 @NonNull ICompanionDeviceService service, boolean isConnected) { 116 if (DEBUG) { 117 Log.d(TAG, "onServiceConnection_StatusChanged() " + mComponentName.toShortString() 118 + " connected=" + isConnected); 119 } 120 } 121 122 // This method is only called when app is force-closed via settings, 123 // but does not handle crashes. Binder death should be handled in #binderDied() 124 @Override onBindingDied(@onNull ComponentName name)125 public void onBindingDied(@NonNull ComponentName name) { 126 // IMPORTANT: call super! this will also invoke binderDied() 127 super.onBindingDied(name); 128 129 if (DEBUG) Log.d(TAG, "onBindingDied() " + mComponentName.toShortString()); 130 } 131 132 @Override binderDied()133 public void binderDied() { 134 super.binderDied(); 135 136 if (DEBUG) Log.d(TAG, "binderDied() " + mComponentName.toShortString()); 137 138 // Handle primary process being killed 139 if (mListener != null) { 140 mListener.onBindingDied(mUserId, mComponentName.getPackageName()); 141 } 142 } 143 144 @Override binderAsInterface(@onNull IBinder service)145 protected ICompanionDeviceService binderAsInterface(@NonNull IBinder service) { 146 return ICompanionDeviceService.Stub.asInterface(service); 147 } 148 149 /** 150 * Overrides {@link ServiceConnector.Impl#getJobHandler()} to provide an alternative Thread 151 * ("in form of" a {@link Handler}) to process jobs on. 152 * <p> 153 * (By default, {@link ServiceConnector.Impl} process jobs on the 154 * {@link android.os.Looper#getMainLooper() MainThread} which is a shared singleton thread 155 * within system_server and thus tends to get heavily congested) 156 */ 157 @Override getJobHandler()158 protected @NonNull Handler getJobHandler() { 159 return getServiceThread().getThreadHandler(); 160 } 161 162 @Override getAutoDisconnectTimeoutMs()163 protected long getAutoDisconnectTimeoutMs() { 164 // Do NOT auto-disconnect. 165 return -1; 166 } 167 buildIntent(@onNull ComponentName componentName)168 private static @NonNull Intent buildIntent(@NonNull ComponentName componentName) { 169 return new Intent(CompanionDeviceService.SERVICE_INTERFACE) 170 .setComponent(componentName); 171 } 172 getServiceThread()173 private static @NonNull ServiceThread getServiceThread() { 174 if (sServiceThread == null) { 175 synchronized (CompanionDeviceManagerService.class) { 176 if (sServiceThread == null) { 177 sServiceThread = new ServiceThread("companion-device-service-connector", 178 THREAD_PRIORITY_DEFAULT, /* allowIo */ false); 179 sServiceThread.start(); 180 } 181 } 182 } 183 return sServiceThread; 184 } 185 186 /** 187 * A worker thread for the {@link ServiceConnector} to process jobs on. 188 * 189 * <p> 190 * Do NOT reference directly, use {@link #getServiceThread()} method instead. 191 */ 192 private static volatile @Nullable ServiceThread sServiceThread; 193 } 194