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 package com.android.car.stream;
17 
18 import android.app.Service;
19 import android.content.Intent;
20 import android.os.Binder;
21 import android.os.DeadObjectException;
22 import android.os.IBinder;
23 import android.os.RemoteException;
24 import android.util.Log;
25 import android.util.Pair;
26 
27 import java.util.ArrayList;
28 import java.util.Iterator;
29 import java.util.LinkedHashMap;
30 import java.util.List;
31 
32 /**
33  * A service that manages the {@link StreamCard} being generated by the system and notifies
34  * the {@link IStreamConsumer} that new cards are available.
35  */
36 public class StreamService extends Service {
37     private static final String TAG = "StreamService";
38     private static final int DEFAULT_STREAM_CONSUMER_COUNT = 3;
39 
40     // The StreamCard is identified by a key which is comprised of its type and id
41     private LinkedHashMap<Pair<Integer, Long>, StreamCard> mStreamCards = new LinkedHashMap<>();
42 
43     private List<IStreamConsumer> mConsumers = new ArrayList<>(DEFAULT_STREAM_CONSUMER_COUNT);
44 
45     private final IBinder mStreamProducerBinder = new StreamProducerBinder();
46 
47 
48     public class StreamProducerBinder extends Binder {
getService()49         StreamService getService() {
50             return StreamService.this;
51         }
52     }
53 
54     @Override
onBind(Intent intent)55     public IBinder onBind(Intent intent) {
56         if (Log.isLoggable(TAG, Log.DEBUG)) {
57             Log.d(TAG, "onBind() calling process ID: " + Binder.getCallingPid()
58                     + " StreamService process ID: " + android.os.Process.myPid());
59         }
60 
61         String action = intent.getAction();
62         switch(action){
63             case StreamConstants.STREAM_PRODUCER_BIND_ACTION:
64                 return mStreamProducerBinder;
65             case StreamConstants.STREAM_CONSUMER_BIND_ACTION:
66                 return mStreamConsumerService;
67             default:
68                 return null;
69         }
70     }
71 
72     private final IBinder mStreamConsumerService = new IStreamService.Stub() {
73         @Override
74         public void registerConsumer(IStreamConsumer consumer) throws RemoteException {
75             mConsumers.add(consumer);
76 
77             if (Log.isLoggable(TAG, Log.DEBUG)) {
78                 Log.d(TAG, "Consumer registered, total # consumers: " + mConsumers.size());
79             }
80         }
81 
82         @Override
83         public void unregisterConsumer(IStreamConsumer consumer) throws RemoteException {
84             mConsumers.remove(consumer);
85 
86             if (Log.isLoggable(TAG, Log.DEBUG)) {
87                 Log.d(TAG, "Consumer removed, total # consumers: " + mConsumers.size());
88             }
89         }
90 
91         @Override
92         public List<StreamCard> fetchAllStreamCards() throws RemoteException {
93             if (Log.isLoggable(TAG, Log.DEBUG)) {
94                 Log.d(TAG, "Fetching all stream items, # cards: " + mStreamCards.size());
95             }
96 
97             List<StreamCard> cards = new ArrayList(mStreamCards.values());
98             return cards;
99         }
100 
101         @Override
102         public void notifyStreamCardDismissed(StreamCard card) throws RemoteException {
103             if (Log.isLoggable(TAG, Log.DEBUG)) {
104                 Log.d(TAG, "StreamCard dismissed");
105             }
106         }
107 
108         @Override
109         public void notifyStreamCardInteracted(StreamCard card) throws RemoteException {
110             if (Log.isLoggable(TAG, Log.DEBUG)) {
111                 Log.d(TAG, "StreamCard clicked");
112             }
113         }
114     };
115 
116     /**
117      * Add a {@link StreamCard} to the StreamService. The {@link StreamCard} will be published to
118      * all IStreamListener registered with the StreamService.
119      */
addStreamCard(StreamCard card)120     public void addStreamCard(StreamCard card) {
121         if (card == null) {
122             return;
123         }
124         rankStreamCard(card);
125         mStreamCards.put(getStreamCardKey(card), card);
126         notifyListenersCardAdded(card);
127     }
128 
129     /**
130      * Remove a {@link StreamCard} to the StreamService. All registered {@link IStreamConsumer} will
131      * be notified of the removal.
132      *
133      * @param card
134      */
removeStreamCard(StreamCard card)135     public void removeStreamCard(StreamCard card) {
136         if (Log.isLoggable(TAG, Log.DEBUG)) {
137             Log.d(TAG, "Stream Card Removed: " + card.toString());
138         }
139 
140         if (card == null) {
141             return;
142         }
143 
144         mStreamCards.remove(getStreamCardKey(card));
145         notifyListenersCardRemoved(card);
146     }
147 
getStreamCardKey(StreamCard card)148     private Pair<Integer, Long> getStreamCardKey(StreamCard card) {
149         return new Pair(card.getType(), card.getId());
150     }
151 
notifyListenersCardAdded(StreamCard card)152     private void notifyListenersCardAdded(StreamCard card) {
153         Iterator<IStreamConsumer> iterator = mConsumers.iterator();
154 
155         while (iterator.hasNext()) {
156             IStreamConsumer consumer = iterator.next();
157             try {
158                 consumer.onStreamCardAdded(card);
159             } catch (DeadObjectException e) {
160                 iterator.remove();
161                 Log.w(TAG, "Dead Stream Listener removed");
162             } catch (RemoteException e) {
163                 Log.e(TAG, e.getMessage());
164             }
165         }
166 
167         if (Log.isLoggable(TAG, Log.DEBUG)) {
168             Log.d(TAG, "Notify StreamCard added, card: " + card);
169             Log.d(TAG, "Card Extension: " + card.getCardExtension());
170         }
171     }
172 
notifyListenersCardRemoved(StreamCard card)173     private void notifyListenersCardRemoved(StreamCard card) {
174         Iterator<IStreamConsumer> iterator = mConsumers.iterator();
175 
176         while (iterator.hasNext()) {
177             IStreamConsumer consumer = iterator.next();
178             try {
179                 consumer.onStreamCardRemoved(card);
180             } catch (DeadObjectException e) {
181                 iterator.remove();
182                 Log.w(TAG, "Dead Stream Listener removed");
183             } catch (RemoteException e) {
184                 Log.e(TAG, e.getMessage());
185             }
186         }
187 
188         if (Log.isLoggable(TAG, Log.DEBUG)) {
189             Log.d(TAG, "Notify StreamCard removed, card type: " + card.getType());
190         }
191     }
192 
rankStreamCard(StreamCard card)193     private void rankStreamCard(StreamCard card) {
194         // TODO: move this into a separate class once we introduce the actual ranking.
195         card.setPriority(1);
196     }
197 }
198