/* * Copyright (c) 2016, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.car.stream; import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.DeadObjectException; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.util.Pair; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; /** * A service that manages the {@link StreamCard} being generated by the system and notifies * the {@link IStreamConsumer} that new cards are available. */ public class StreamService extends Service { private static final String TAG = "StreamService"; private static final int DEFAULT_STREAM_CONSUMER_COUNT = 3; // The StreamCard is identified by a key which is comprised of its type and id private LinkedHashMap, StreamCard> mStreamCards = new LinkedHashMap<>(); private List mConsumers = new ArrayList<>(DEFAULT_STREAM_CONSUMER_COUNT); private final IBinder mStreamProducerBinder = new StreamProducerBinder(); public class StreamProducerBinder extends Binder { StreamService getService() { return StreamService.this; } } @Override public IBinder onBind(Intent intent) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "onBind() calling process ID: " + Binder.getCallingPid() + " StreamService process ID: " + android.os.Process.myPid()); } String action = intent.getAction(); switch(action){ case StreamConstants.STREAM_PRODUCER_BIND_ACTION: return mStreamProducerBinder; case StreamConstants.STREAM_CONSUMER_BIND_ACTION: return mStreamConsumerService; default: return null; } } private final IBinder mStreamConsumerService = new IStreamService.Stub() { @Override public void registerConsumer(IStreamConsumer consumer) throws RemoteException { mConsumers.add(consumer); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Consumer registered, total # consumers: " + mConsumers.size()); } } @Override public void unregisterConsumer(IStreamConsumer consumer) throws RemoteException { mConsumers.remove(consumer); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Consumer removed, total # consumers: " + mConsumers.size()); } } @Override public List fetchAllStreamCards() throws RemoteException { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Fetching all stream items, # cards: " + mStreamCards.size()); } List cards = new ArrayList(mStreamCards.values()); return cards; } @Override public void notifyStreamCardDismissed(StreamCard card) throws RemoteException { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "StreamCard dismissed"); } } @Override public void notifyStreamCardInteracted(StreamCard card) throws RemoteException { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "StreamCard clicked"); } } }; /** * Add a {@link StreamCard} to the StreamService. The {@link StreamCard} will be published to * all IStreamListener registered with the StreamService. */ public void addStreamCard(StreamCard card) { if (card == null) { return; } rankStreamCard(card); mStreamCards.put(getStreamCardKey(card), card); notifyListenersCardAdded(card); } /** * Remove a {@link StreamCard} to the StreamService. All registered {@link IStreamConsumer} will * be notified of the removal. * * @param card */ public void removeStreamCard(StreamCard card) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Stream Card Removed: " + card.toString()); } if (card == null) { return; } mStreamCards.remove(getStreamCardKey(card)); notifyListenersCardRemoved(card); } private Pair getStreamCardKey(StreamCard card) { return new Pair(card.getType(), card.getId()); } private void notifyListenersCardAdded(StreamCard card) { Iterator iterator = mConsumers.iterator(); while (iterator.hasNext()) { IStreamConsumer consumer = iterator.next(); try { consumer.onStreamCardAdded(card); } catch (DeadObjectException e) { iterator.remove(); Log.w(TAG, "Dead Stream Listener removed"); } catch (RemoteException e) { Log.e(TAG, e.getMessage()); } } if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Notify StreamCard added, card: " + card); Log.d(TAG, "Card Extension: " + card.getCardExtension()); } } private void notifyListenersCardRemoved(StreamCard card) { Iterator iterator = mConsumers.iterator(); while (iterator.hasNext()) { IStreamConsumer consumer = iterator.next(); try { consumer.onStreamCardRemoved(card); } catch (DeadObjectException e) { iterator.remove(); Log.w(TAG, "Dead Stream Listener removed"); } catch (RemoteException e) { Log.e(TAG, e.getMessage()); } } if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Notify StreamCard removed, card type: " + card.getType()); } } private void rankStreamCard(StreamCard card) { // TODO: move this into a separate class once we introduce the actual ranking. card.setPriority(1); } }