Home | History | Annotate | Download | only in print
      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