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 android.printservice.recommendation;
18 
19 import android.annotation.Nullable;
20 import android.annotation.SystemApi;
21 import android.app.Service;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.os.Handler;
25 import android.os.IBinder;
26 import android.os.Looper;
27 import android.os.Message;
28 import android.os.RemoteException;
29 import android.util.Log;
30 
31 import java.util.List;
32 
33 /**
34  * Base class for the print service recommendation services.
35  *
36  * @hide
37  */
38 @SystemApi
39 public abstract class RecommendationService extends Service {
40     private static final String LOG_TAG = "PrintServiceRecS";
41 
42     /** Used to push onConnect and onDisconnect on the main thread */
43     private Handler mHandler;
44 
45     /**
46      * The {@link Intent} action that must be declared as handled by a service in its manifest for
47      * the system to recognize it as a print service recommendation service.
48      */
49     public static final String SERVICE_INTERFACE =
50             "android.printservice.recommendation.RecommendationService";
51 
52     /** Registered callbacks, only modified on main thread */
53     private IRecommendationServiceCallbacks mCallbacks;
54 
55     @Override
attachBaseContext(Context base)56     protected void attachBaseContext(Context base) {
57         super.attachBaseContext(base);
58 
59         mHandler = new MyHandler();
60     }
61 
62     /**
63      * Update the print service recommendations.
64      *
65      * @param recommendations The new set of recommendations
66      */
updateRecommendations(@ullable List<RecommendationInfo> recommendations)67     public final void updateRecommendations(@Nullable List<RecommendationInfo> recommendations) {
68         mHandler.obtainMessage(MyHandler.MSG_UPDATE, recommendations).sendToTarget();
69     }
70 
71     @Override
onBind(Intent intent)72     public final IBinder onBind(Intent intent) {
73         return new IRecommendationService.Stub() {
74             @Override
75             public void registerCallbacks(IRecommendationServiceCallbacks callbacks) {
76                 // The callbacks come in order of the caller on oneway calls. Hence while the caller
77                 // cannot know at what time the connection is made, he can know the ordering of
78                 // connection and disconnection.
79                 //
80                 // Similar he cannot know when the disconnection is processed, hence he has to
81                 // handle callbacks after calling disconnect.
82                 if (callbacks != null) {
83                     mHandler.obtainMessage(MyHandler.MSG_CONNECT, callbacks).sendToTarget();
84                 } else {
85                     mHandler.obtainMessage(MyHandler.MSG_DISCONNECT).sendToTarget();
86                 }
87             }
88         };
89     }
90 
91     /**
92      * Called when the client connects to the recommendation service.
93      */
94     public abstract void onConnected();
95 
96     /**
97      * Called when the client disconnects from the recommendation service.
98      */
99     public abstract void onDisconnected();
100 
101     private class MyHandler extends Handler {
102         static final int MSG_CONNECT = 1;
103         static final int MSG_DISCONNECT = 2;
104         static final int MSG_UPDATE = 3;
105 
106         MyHandler() {
107             super(Looper.getMainLooper());
108         }
109 
110         @Override
111         public void handleMessage(Message msg) {
112             switch (msg.what) {
113                 case MSG_CONNECT:
114                     mCallbacks = (IRecommendationServiceCallbacks) msg.obj;
115                     onConnected();
116                     break;
117                 case MSG_DISCONNECT:
118                     onDisconnected();
119                     mCallbacks = null;
120                     break;
121                 case MSG_UPDATE:
122                     if (mCallbacks != null) {
123                         // Note that there might be a connection change in progress. In this case
124                         // the message is handled as before the change. This is acceptable as the
125                         // caller of the connection change has not guarantee when the connection
126                         // change binder transaction is actually processed.
127                         try {
128                             mCallbacks.onRecommendationsUpdated((List<RecommendationInfo>) msg.obj);
129                         } catch (RemoteException | NullPointerException e) {
130                             Log.e(LOG_TAG, "Could not update recommended services", e);
131                         }
132                     }
133                     break;
134             }
135         }
136     }
137 }
138