1 /* 2 * Copyright (C) 2016 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.print; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.ServiceConnection; 25 import android.content.pm.ApplicationInfo; 26 import android.content.pm.ResolveInfo; 27 import android.os.IBinder; 28 import android.os.RemoteException; 29 import android.os.UserHandle; 30 import android.printservice.recommendation.IRecommendationService; 31 import android.printservice.recommendation.IRecommendationServiceCallbacks; 32 import android.printservice.recommendation.RecommendationInfo; 33 import android.util.Log; 34 import com.android.internal.annotations.GuardedBy; 35 import com.android.internal.util.Preconditions; 36 37 import java.util.List; 38 39 import static android.content.pm.PackageManager.GET_META_DATA; 40 import static android.content.pm.PackageManager.GET_SERVICES; 41 import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING; 42 43 /** 44 * Connection to a remote print service recommendation service. 45 */ 46 class RemotePrintServiceRecommendationService { 47 private static final String LOG_TAG = "RemotePrintServiceRecS"; 48 49 /** Lock for this object */ 50 private final Object mLock = new Object(); 51 52 /** Context used for the connection */ 53 private @NonNull final Context mContext; 54 55 /** The connection to the service (if {@link #mIsBound bound}) */ 56 @GuardedBy("mLock") 57 private @NonNull final Connection mConnection; 58 59 /** If the service is currently bound. */ 60 @GuardedBy("mLock") 61 private boolean mIsBound; 62 63 /** The service once bound */ 64 @GuardedBy("mLock") 65 private IRecommendationService mService; 66 67 /** 68 * Callbacks to be called when there are updates to the print service recommendations. 69 */ 70 public interface RemotePrintServiceRecommendationServiceCallbacks { 71 /** 72 * Called when there is an update list of print service recommendations. 73 * 74 * @param recommendations The new recommendations. 75 */ 76 void onPrintServiceRecommendationsUpdated( 77 @Nullable List<RecommendationInfo> recommendations); 78 } 79 80 /** 81 * @return The intent that is used to connect to the print service recommendation service. 82 */ 83 private Intent getServiceIntent(@NonNull UserHandle userHandle) throws Exception { 84 List<ResolveInfo> installedServices = mContext.getPackageManager() 85 .queryIntentServicesAsUser(new Intent( 86 android.printservice.recommendation.RecommendationService.SERVICE_INTERFACE), 87 GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING, 88 userHandle.getIdentifier()); 89 90 if (installedServices.size() != 1) { 91 throw new Exception(installedServices.size() + " instead of exactly one service found"); 92 } 93 94 ResolveInfo installedService = installedServices.get(0); 95 96 ComponentName serviceName = new ComponentName( 97 installedService.serviceInfo.packageName, 98 installedService.serviceInfo.name); 99 100 ApplicationInfo appInfo = mContext.getPackageManager() 101 .getApplicationInfo(installedService.serviceInfo.packageName, 0); 102 103 if (appInfo == null) { 104 throw new Exception("Cannot read appInfo for service"); 105 } 106 107 if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { 108 throw new Exception("Service is not part of the system"); 109 } 110 111 if (!android.Manifest.permission.BIND_PRINT_RECOMMENDATION_SERVICE.equals( 112 installedService.serviceInfo.permission)) { 113 throw new Exception("Service " + serviceName.flattenToShortString() 114 + " does not require permission " 115 + android.Manifest.permission.BIND_PRINT_RECOMMENDATION_SERVICE); 116 } 117 118 Intent serviceIntent = new Intent(); 119 serviceIntent.setComponent(serviceName); 120 121 return serviceIntent; 122 } 123 124 /** 125 * Open a new connection to a {@link IRecommendationService remote print service 126 * recommendation service}. 127 * 128 * @param context The context establishing the connection 129 * @param userHandle The user the connection is for 130 * @param callbacks The callbacks to call by the service 131 */ 132 RemotePrintServiceRecommendationService(@NonNull Context context, 133 @NonNull UserHandle userHandle, 134 @NonNull RemotePrintServiceRecommendationServiceCallbacks callbacks) { 135 mContext = context; 136 mConnection = new Connection(callbacks); 137 138 try { 139 Intent serviceIntent = getServiceIntent(userHandle); 140 141 synchronized (mLock) { 142 mIsBound = mContext.bindServiceAsUser(serviceIntent, mConnection, 143 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, userHandle); 144 145 if (!mIsBound) { 146 throw new Exception("Failed to bind to service " + serviceIntent); 147 } 148 } 149 } catch (Exception e) { 150 Log.e(LOG_TAG, "Could not connect to print service recommendation service", e); 151 } 152 } 153 154 /** 155 * Terminate the connection to the {@link IRecommendationService remote print 156 * service recommendation service}. 157 */ 158 void close() { 159 synchronized (mLock) { 160 if (mService != null) { 161 try { 162 mService.registerCallbacks(null); 163 } catch (RemoteException e) { 164 Log.e(LOG_TAG, "Could not unregister callbacks", e); 165 } 166 167 mService = null; 168 } 169 170 if (mIsBound) { 171 mContext.unbindService(mConnection); 172 mIsBound = false; 173 } 174 } 175 } 176 177 @Override 178 protected void finalize() throws Throwable { 179 if (mIsBound || mService != null) { 180 Log.w(LOG_TAG, "Service still connected on finalize()"); 181 close(); 182 } 183 184 super.finalize(); 185 } 186 187 /** 188 * Connection to the service. 189 */ 190 private class Connection implements ServiceConnection { 191 private final RemotePrintServiceRecommendationServiceCallbacks mCallbacks; 192 193 public Connection(@NonNull RemotePrintServiceRecommendationServiceCallbacks callbacks) { 194 mCallbacks = callbacks; 195 } 196 197 @Override 198 public void onServiceConnected(ComponentName name, IBinder service) { 199 synchronized (mLock) { 200 mService = (IRecommendationService)IRecommendationService.Stub.asInterface(service); 201 202 try { 203 mService.registerCallbacks(new IRecommendationServiceCallbacks.Stub() { 204 @Override 205 public void onRecommendationsUpdated( 206 List<RecommendationInfo> recommendations) { 207 synchronized (mLock) { 208 if (mIsBound && mService != null) { 209 if (recommendations != null) { 210 Preconditions.checkCollectionElementsNotNull( 211 recommendations, "recommendation"); 212 } 213 214 mCallbacks.onPrintServiceRecommendationsUpdated( 215 recommendations); 216 } 217 } 218 } 219 }); 220 } catch (RemoteException e) { 221 Log.e(LOG_TAG, "Could not register callbacks", e); 222 } 223 } 224 } 225 226 @Override 227 public void onServiceDisconnected(ComponentName name) { 228 Log.w(LOG_TAG, "Unexpected termination of connection"); 229 230 synchronized (mLock) { 231 mService = null; 232 } 233 } 234 } 235 } 236