diff options
author | Jae Seo <jaeseo@google.com> | 2014-02-20 18:23:25 -0800 |
---|---|---|
committer | Jae Seo <jaeseo@google.com> | 2014-04-08 13:35:21 -0700 |
commit | 3957091ba8f08c02b5e781098cb955a5f697a1ff (patch) | |
tree | c8739c677c87e62ea6c1e8bc45de027d1e65d87c /core/java/android/tv | |
parent | 53c2cf799fddfae7f6fc9ca1840ea345308b79ee (diff) | |
download | frameworks_base-3957091ba8f08c02b5e781098cb955a5f697a1ff.zip frameworks_base-3957091ba8f08c02b5e781098cb955a5f697a1ff.tar.gz frameworks_base-3957091ba8f08c02b5e781098cb955a5f697a1ff.tar.bz2 |
Initial round of Television Input Framework
This provides APIs to control and create individual television inputs on
the system which will later be hosted by television applications.
Change-Id: I6866d28e78175a1bff2c32a85c5d77e94d0cd60c
Diffstat (limited to 'core/java/android/tv')
-rw-r--r-- | core/java/android/tv/ITvInputClient.aidl | 30 | ||||
-rw-r--r-- | core/java/android/tv/ITvInputManager.aidl | 43 | ||||
-rw-r--r-- | core/java/android/tv/ITvInputService.aidl | 31 | ||||
-rw-r--r-- | core/java/android/tv/ITvInputServiceCallback.aidl | 28 | ||||
-rw-r--r-- | core/java/android/tv/ITvInputSession.aidl | 34 | ||||
-rw-r--r-- | core/java/android/tv/ITvInputSessionCallback.aidl | 28 | ||||
-rw-r--r-- | core/java/android/tv/ITvInputSessionWrapper.java | 99 | ||||
-rw-r--r-- | core/java/android/tv/TvInputInfo.aidl | 19 | ||||
-rw-r--r-- | core/java/android/tv/TvInputInfo.java | 124 | ||||
-rw-r--r-- | core/java/android/tv/TvInputManager.java | 392 | ||||
-rw-r--r-- | core/java/android/tv/TvInputService.java | 254 | ||||
-rw-r--r-- | core/java/android/tv/TvInputSession.java | 57 |
12 files changed, 1139 insertions, 0 deletions
diff --git a/core/java/android/tv/ITvInputClient.aidl b/core/java/android/tv/ITvInputClient.aidl new file mode 100644 index 0000000..43be6f0 --- /dev/null +++ b/core/java/android/tv/ITvInputClient.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 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 android.tv; + +import android.content.ComponentName; +import android.tv.ITvInputSession; + +/** + * Interface a client of the ITvInputManager implements, to identify itself and receive information + * about changes to the state of each TV input service. + * @hide + */ +oneway interface ITvInputClient { + void onSessionCreated(in ComponentName name, IBinder token, int seq); + void onAvailabilityChanged(in ComponentName name, boolean isAvailable); +} diff --git a/core/java/android/tv/ITvInputManager.aidl b/core/java/android/tv/ITvInputManager.aidl new file mode 100644 index 0000000..a927dc9 --- /dev/null +++ b/core/java/android/tv/ITvInputManager.aidl @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2014 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 android.tv; + +import android.content.ComponentName; +import android.net.Uri; +import android.tv.ITvInputClient; +import android.tv.TvInputInfo; +import android.view.Surface; + +/** + * Interface to the TV input manager service. + * @hide + */ +interface ITvInputManager { + List<TvInputInfo> getTvInputList(int userId); + + boolean getAvailability(in ITvInputClient client, in ComponentName name, int userId); + + void registerCallback(in ITvInputClient client, in ComponentName name, int userId); + void unregisterCallback(in ITvInputClient client, in ComponentName name, int userId); + + void createSession(in ITvInputClient client, in ComponentName name, int seq, int userId); + void releaseSession(in IBinder sessionToken, int userId); + + void setSurface(in IBinder sessionToken, in Surface surface, int userId); + void setVolume(in IBinder sessionToken, float volume, int userId); + void tune(in IBinder sessionToken, in Uri channelUri, int userId); +} diff --git a/core/java/android/tv/ITvInputService.aidl b/core/java/android/tv/ITvInputService.aidl new file mode 100644 index 0000000..d80f286 --- /dev/null +++ b/core/java/android/tv/ITvInputService.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2014 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 android.tv; + +import android.tv.ITvInputServiceCallback; +import android.tv.ITvInputSession; +import android.tv.ITvInputSessionCallback; + +/** + * Top-level interface to a TV input component (implemented in a Service). + * @hide + */ +oneway interface ITvInputService { + void registerCallback(ITvInputServiceCallback callback); + void unregisterCallback(in ITvInputServiceCallback callback); + void createSession(ITvInputSessionCallback callback); +} diff --git a/core/java/android/tv/ITvInputServiceCallback.aidl b/core/java/android/tv/ITvInputServiceCallback.aidl new file mode 100644 index 0000000..e535c81 --- /dev/null +++ b/core/java/android/tv/ITvInputServiceCallback.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2014 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 android.tv; + +import android.content.ComponentName; + +/** + * Helper interface for ITvInputService to allow the TV input to notify the client when its status + * has been changed. + * @hide + */ +oneway interface ITvInputServiceCallback { + void onAvailabilityChanged(in ComponentName name, boolean isAvailable); +} diff --git a/core/java/android/tv/ITvInputSession.aidl b/core/java/android/tv/ITvInputSession.aidl new file mode 100644 index 0000000..d379d2d --- /dev/null +++ b/core/java/android/tv/ITvInputSession.aidl @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2014 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 android.tv; + +import android.net.Uri; +import android.view.Surface; + +/** + * Sub-interface of ITvInputService which is created per session and has its own context. + * @hide + */ +oneway interface ITvInputSession { + void release(); + + void setSurface(in Surface surface); + // TODO: Remove this once it becomes irrelevant for applications to handle audio focus. The plan + // is to introduce some new concepts that will solve a number of problems in audio policy today. + void setVolume(float volume); + void tune(in Uri channelUri); +} diff --git a/core/java/android/tv/ITvInputSessionCallback.aidl b/core/java/android/tv/ITvInputSessionCallback.aidl new file mode 100644 index 0000000..a2bd0d7 --- /dev/null +++ b/core/java/android/tv/ITvInputSessionCallback.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2014 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 android.tv; + +import android.tv.ITvInputSession; + +/** + * Helper interface for ITvInputSession to allow the TV input to notify the system service when a + * new session has been created. + * @hide + */ +oneway interface ITvInputSessionCallback { + void onSessionCreated(ITvInputSession session); +} diff --git a/core/java/android/tv/ITvInputSessionWrapper.java b/core/java/android/tv/ITvInputSessionWrapper.java new file mode 100644 index 0000000..fd4e1e3 --- /dev/null +++ b/core/java/android/tv/ITvInputSessionWrapper.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2014 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 android.tv; + +import android.content.Context; +import android.net.Uri; +import android.os.Message; +import android.util.Log; +import android.view.Surface; + +import com.android.internal.os.HandlerCaller; + +/** + * Implements the internal ITvInputSession interface to convert incoming calls on to it back to + * calls on the public TvInputSession interface, scheduling them on the main thread of the process. + * + * @hide + */ +public class ITvInputSessionWrapper extends ITvInputSession.Stub implements HandlerCaller.Callback { + private static final String TAG = "TvInputSessionWrapper"; + + private static final int DO_RELEASE = 1; + private static final int DO_SET_SURFACE = 2; + private static final int DO_SET_VOLUME = 3; + private static final int DO_TUNE = 4; + + private TvInputSession mTvInputSession; + private final HandlerCaller mCaller; + + public ITvInputSessionWrapper(Context context, TvInputSession session) { + mCaller = new HandlerCaller(context, null, this, true /* asyncHandler */); + mTvInputSession = session; + } + + @Override + public void executeMessage(Message msg) { + if (mTvInputSession == null) { + return; + } + + switch (msg.what) { + case DO_RELEASE: { + mTvInputSession.release(); + mTvInputSession = null; + return; + } + case DO_SET_SURFACE: { + mTvInputSession.setSurface((Surface) msg.obj); + return; + } + case DO_SET_VOLUME: { + mTvInputSession.setVolume((Float) msg.obj); + return; + } + case DO_TUNE: { + mTvInputSession.tune((Uri) msg.obj); + return; + } + default: { + Log.w(TAG, "Unhandled message code: " + msg.what); + return; + } + } + } + + @Override + public void release() { + mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE)); + } + + @Override + public void setSurface(Surface surface) { + mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_SURFACE, surface)); + } + + @Override + public final void setVolume(float volume) { + mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_VOLUME, volume)); + } + + @Override + public void tune(Uri channelUri) { + mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_TUNE, channelUri)); + } +} diff --git a/core/java/android/tv/TvInputInfo.aidl b/core/java/android/tv/TvInputInfo.aidl new file mode 100644 index 0000000..abc4b47 --- /dev/null +++ b/core/java/android/tv/TvInputInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2014 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 android.tv; + +parcelable TvInputInfo; diff --git a/core/java/android/tv/TvInputInfo.java b/core/java/android/tv/TvInputInfo.java new file mode 100644 index 0000000..90625d8 --- /dev/null +++ b/core/java/android/tv/TvInputInfo.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2014 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 android.tv; + +import android.content.ComponentName; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class is used to specify meta information of a TV input. + */ +public final class TvInputInfo implements Parcelable { + private final ResolveInfo mService; + private final String mId; + + /** + * Constructor. + * + * @param service The ResolveInfo returned from the package manager about this TV input service. + * @hide + */ + public TvInputInfo(ResolveInfo service) { + mService = service; + ServiceInfo si = service.serviceInfo; + mId = new ComponentName(si.packageName, si.name).flattenToShortString(); + } + + /** + * Returns a unique ID for this TV input. The ID is generated from the package and class name + * implementing the TV input service. + */ + public String getId() { + return mId; + } + + /** + * Returns the .apk package that implements this TV input service. + */ + public String getPackageName() { + return mService.serviceInfo.packageName; + } + + /** + * Returns the class name of the service component that implements this TV input service. + */ + public String getServiceName() { + return mService.serviceInfo.name; + } + + /** + * Returns the component of the service that implements this TV input. + */ + public ComponentName getComponent() { + return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name); + } + + /** + * Loads the user-displayed label for this TV input service. + * + * @param pm Supplies a PackageManager used to load the TV input's resources. + * @return Returns a CharSequence containing the TV input's label. If the TV input does not have + * a label, its name is returned. + */ + public CharSequence loadLabel(PackageManager pm) { + return mService.loadLabel(pm); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Used to package this object into a {@link Parcel}. + * + * @param dest The {@link Parcel} to be written. + * @param flags The flags used for parceling. + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mId); + mService.writeToParcel(dest, flags); + } + + /** + * Used to make this class parcelable. + * + * @hide + */ + public static final Parcelable.Creator<TvInputInfo> CREATOR = + new Parcelable.Creator<TvInputInfo>() { + @Override + public TvInputInfo createFromParcel(Parcel in) { + return new TvInputInfo(in); + } + + @Override + public TvInputInfo[] newArray(int size) { + return new TvInputInfo[size]; + } + }; + + private TvInputInfo(Parcel in) { + mId = in.readString(); + mService = ResolveInfo.CREATOR.createFromParcel(in); + } +} diff --git a/core/java/android/tv/TvInputManager.java b/core/java/android/tv/TvInputManager.java new file mode 100644 index 0000000..0b6ab64 --- /dev/null +++ b/core/java/android/tv/TvInputManager.java @@ -0,0 +1,392 @@ +/* + * Copyright (C) 2014 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 android.tv; + +import android.content.ComponentName; +import android.net.Uri; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.util.SparseArray; +import android.view.Surface; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Central system API to the overall TV input framework (TIF) architecture, which arbitrates + * interaction between applications and the selected TV inputs. + */ +public final class TvInputManager { + private static final String TAG = "TvInputManager"; + + private final ITvInputManager mService; + + // A mapping from an input to the list of its TvInputListenerRecords. + private final Map<ComponentName, List<TvInputListenerRecord>> mTvInputListenerRecordsMap = + new HashMap<ComponentName, List<TvInputListenerRecord>>(); + + // A mapping from the sequence number of a session to its SessionCreateCallbackRecord. + private final SparseArray<SessionCreateCallbackRecord> mSessionCreateCallbackRecordMap = + new SparseArray<SessionCreateCallbackRecord>(); + + // A sequence number for the next session to be created. Should be protected by a lock + // {@code mSessionCreateCallbackRecordMap}. + private int mNextSeq; + + private final ITvInputClient mClient; + + private final int mUserId; + + /** + * Interface used to receive the created session. + */ + public interface SessionCreateCallback { + /** + * This is called after {@link TvInputManager#createSession} has been processed. + * + * @param session A {@link TvInputManager.Session} instance created. This can be + * {@code null} if the creation request failed. + */ + void onSessionCreated(Session session); + } + + private static final class SessionCreateCallbackRecord { + private final SessionCreateCallback mSessionCreateCallback; + private final Handler mHandler; + + public SessionCreateCallbackRecord(SessionCreateCallback sessionCreateCallback, + Handler handler) { + mSessionCreateCallback = sessionCreateCallback; + mHandler = handler; + } + + public void postSessionCreated(final Session session) { + mHandler.post(new Runnable() { + @Override + public void run() { + mSessionCreateCallback.onSessionCreated(session); + } + }); + } + } + + /** + * Interface used to monitor status of the TV input. + */ + public abstract static class TvInputListener { + /** + * This is called when the availability status of a given TV input is changed. + * + * @param name {@link ComponentName} of {@link android.app.Service} that implements the + * given TV input. + * @param isAvailable {@code true} if the given TV input is available to show TV programs. + * {@code false} otherwise. + */ + public void onAvailabilityChanged(ComponentName name, boolean isAvailable) { + } + } + + private static final class TvInputListenerRecord { + private final TvInputListener mListener; + private final Handler mHandler; + + public TvInputListenerRecord(TvInputListener listener, Handler handler) { + mListener = listener; + mHandler = handler; + } + + public TvInputListener getListener() { + return mListener; + } + + public void postAvailabilityChanged(final ComponentName name, final boolean isAvailable) { + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onAvailabilityChanged(name, isAvailable); + } + }); + } + } + + /** + * @hide + */ + public TvInputManager(ITvInputManager service, int userId) { + mService = service; + mUserId = userId; + mClient = new ITvInputClient.Stub() { + @Override + public void onSessionCreated(ComponentName name, IBinder token, int seq) { + synchronized (mSessionCreateCallbackRecordMap) { + SessionCreateCallbackRecord record = mSessionCreateCallbackRecordMap.get(seq); + mSessionCreateCallbackRecordMap.delete(seq); + if (record == null) { + Log.e(TAG, "Callback not found for " + token); + return; + } + Session session = null; + if (token != null) { + session = new Session(name, token, mService, mUserId); + } + record.postSessionCreated(session); + } + } + + @Override + public void onAvailabilityChanged(ComponentName name, boolean isAvailable) { + synchronized (mTvInputListenerRecordsMap) { + List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(name); + if (records == null) { + // Silently ignore - no listener is registered yet. + return; + } + int recordsCount = records.size(); + for (int i = 0; i < recordsCount; i++) { + records.get(i).postAvailabilityChanged(name, isAvailable); + } + } + } + }; + } + + /** + * Returns the complete list of TV inputs on the system. + * + * @return List of {@link TvInputInfo} for each TV input that describes its meta information. + */ + public List<TvInputInfo> getTvInputList() { + try { + return mService.getTvInputList(mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Returns the availability of a given TV input. + * + * @param name {@link ComponentName} of {@link android.app.Service} that implements the given TV + * input. + * @throws IllegalArgumentException if the argument is {@code null}. + * @throws IllegalStateException If there is no {@link TvInputListener} registered on the given + * TV input. + */ + public boolean getAvailability(ComponentName name) { + if (name == null) { + throw new IllegalArgumentException("name cannot be null"); + } + synchronized (mTvInputListenerRecordsMap) { + List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(name); + if (records == null || records.size() == 0) { + throw new IllegalStateException("At least one listener should be registered."); + } + } + try { + return mService.getAvailability(mClient, name, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Registers a {@link TvInputListener} for a given TV input. + * + * @param name {@link ComponentName} of {@link android.app.Service} that implements the given TV + * input. + * @param listener a listener used to monitor status of the given TV input. + * @param handler a {@link Handler} that the status change will be delivered to. + * @throws IllegalArgumentException if any of the arguments is {@code null}. + */ + public void registerListener(ComponentName name, TvInputListener listener, Handler handler) { + if (name == null) { + throw new IllegalArgumentException("name cannot be null"); + } + if (listener == null) { + throw new IllegalArgumentException("listener cannot be null"); + } + if (handler == null) { + throw new IllegalArgumentException("handler cannot be null"); + } + synchronized (mTvInputListenerRecordsMap) { + List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(name); + if (records == null) { + records = new ArrayList<TvInputListenerRecord>(); + mTvInputListenerRecordsMap.put(name, records); + try { + mService.registerCallback(mClient, name, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + records.add(new TvInputListenerRecord(listener, handler)); + } + } + + /** + * Unregisters the existing {@link TvInputListener} for a given TV input. + * + * @param name {@link ComponentName} of {@link android.app.Service} that implements the given TV + * input. + * @param listener the existing listener to remove for the given TV input. + * @throws IllegalArgumentException if any of the arguments is {@code null}. + */ + public void unregisterListener(ComponentName name, final TvInputListener listener) { + if (name == null) { + throw new IllegalArgumentException("name cannot be null"); + } + if (listener == null) { + throw new IllegalArgumentException("listener cannot be null"); + } + synchronized (mTvInputListenerRecordsMap) { + List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(name); + if (records == null) { + Log.e(TAG, "No listener found for " + name.getClassName()); + return; + } + for (Iterator<TvInputListenerRecord> it = records.iterator(); it.hasNext();) { + TvInputListenerRecord record = it.next(); + if (record.getListener() == listener) { + it.remove(); + } + } + if (records.isEmpty()) { + try { + mService.unregisterCallback(mClient, name, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } finally { + mTvInputListenerRecordsMap.remove(name); + } + } + } + } + + /** + * Creates a {@link TvInputSession} interface for a given TV input. + * <p> + * The number of sessions that can be created at the same time is limited by the capability of + * the given TV input. + * </p> + * + * @param name {@link ComponentName} of {@link android.app.Service} that implements the given TV + * input. + * @param callback a callback used to receive the created session. + * @param handler a {@link Handler} that the session creation will be delivered to. + * @throws IllegalArgumentException if any of the arguments is {@code null}. + */ + public void createSession(ComponentName name, final SessionCreateCallback callback, + Handler handler) { + if (name == null) { + throw new IllegalArgumentException("name cannot be null"); + } + if (callback == null) { + throw new IllegalArgumentException("callback cannot be null"); + } + if (handler == null) { + throw new IllegalArgumentException("handler cannot be null"); + } + SessionCreateCallbackRecord record = new SessionCreateCallbackRecord(callback, handler); + synchronized (mSessionCreateCallbackRecordMap) { + int seq = mNextSeq++; + mSessionCreateCallbackRecordMap.put(seq, record); + try { + mService.createSession(mClient, name, seq, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + } + + /** The Session provides the per-session functionality of TV inputs. */ + public static final class Session { + private final ITvInputManager mService; + private final IBinder mToken; + private final int mUserId; + + /** @hide */ + private Session(ComponentName name, IBinder token, ITvInputManager service, int userId) { + mToken = token; + mService = service; + mUserId = userId; + } + + /** + * Releases this session. + */ + public void release() { + try { + mService.releaseSession(mToken, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Sets the {@link android.view.Surface} for this session. + * + * @param surface A {@link android.view.Surface} used to render video. + */ + public void setSurface(Surface surface) { + // surface can be null. + try { + mService.setSurface(mToken, surface, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Sets the relative volume of this session to handle a change of audio focus. + * + * @param volume A volume value between 0.0f to 1.0f. + * @throws IllegalArgumentException if the volume value is out of range. + */ + public void setVolume(float volume) { + try { + if (volume < 0.0f || volume > 1.0f) { + throw new IllegalArgumentException("volume should be between 0.0f and 1.0f"); + } + mService.setVolume(mToken, volume, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Tunes to a given channel. + * + * @param channelUri The URI of a channel. + * @throws IllegalArgumentException if the argument is {@code null}. + */ + public void tune(Uri channelUri) { + if (channelUri == null) { + throw new IllegalArgumentException("channelUri cannot be null"); + } + try { + mService.tune(mToken, channelUri, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/core/java/android/tv/TvInputService.java b/core/java/android/tv/TvInputService.java new file mode 100644 index 0000000..e43cc95 --- /dev/null +++ b/core/java/android/tv/TvInputService.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2014 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 android.tv; + +import android.app.Service; +import android.content.ComponentName; +import android.content.Intent; +import android.net.Uri; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.util.Log; +import android.view.Surface; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * A base class for implementing television input service. + */ +public abstract class TvInputService extends Service { + // STOPSHIP: Turn debugging off. + private static final boolean DEBUG = true; + private static final String TAG = "TvInputService"; + + /** + * This is the interface name that a service implementing a TV input should say that it support + * -- that is, this is the action it uses for its intent filter. To be supported, the service + * must also require the {@link android.Manifest.permission#BIND_TV_INPUT} permission so that + * other applications cannot abuse it. + */ + public static final String SERVICE_INTERFACE = "android.tv.TvInputService"; + + private ComponentName mComponentName; + private final Handler mHandler = new ServiceHandler(); + private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks = + new RemoteCallbackList<ITvInputServiceCallback>(); + private boolean mAvailable; + + @Override + public void onCreate() { + super.onCreate(); + mComponentName = new ComponentName(getPackageName(), getClass().getName()); + } + + @Override + public final IBinder onBind(Intent intent) { + return new ITvInputService.Stub() { + @Override + public void registerCallback(ITvInputServiceCallback cb) { + if (cb != null) { + mCallbacks.register(cb); + // The first time a callback is registered, the service needs to report its + // availability status so that the system can know its initial value. + try { + cb.onAvailabilityChanged(mComponentName, mAvailable); + } catch (RemoteException e) { + Log.e(TAG, "error in onAvailabilityChanged", e); + } + } + } + + @Override + public void unregisterCallback(ITvInputServiceCallback cb) { + if (cb != null) { + mCallbacks.unregister(cb); + } + } + + @Override + public void createSession(ITvInputSessionCallback cb) { + if (cb != null) { + mHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, cb).sendToTarget(); + } + } + }; + } + + /** + * Convenience method to notify an availability change of this TV input service. + * + * @param available {@code true} if the input service is available to show TV programs. + */ + public final void setAvailable(boolean available) { + if (available != mAvailable) { + mAvailable = available; + mHandler.obtainMessage(ServiceHandler.DO_BROADCAST_AVAILABILITY_CHANGE, available) + .sendToTarget(); + } + } + + /** + * Get the number of callbacks that are registered. + * + * @hide + */ + @VisibleForTesting + public final int getRegisteredCallbackCount() { + return mCallbacks.getRegisteredCallbackCount(); + } + + /** + * Returns a concrete implementation of {@link TvInputSessionImpl}. + * <p> + * May return {@code null} if this TV input service fails to create a session for some reason. + * </p> + */ + public abstract TvInputSessionImpl onCreateSession(); + + /** + * Base class for derived classes to implement to provide {@link TvInputSession}. + */ + public abstract static class TvInputSessionImpl { + /** + * Called when the session is released. + */ + public abstract void onRelease(); + + /** + * Sets the {@link Surface} for the current input session on which the TV input renders + * video. + * + * @param surface {@link Surface} an application passes to this TV input session. + * @return {@code true} if the surface was set, {@code false} otherwise. + */ + public abstract boolean onSetSurface(Surface surface); + + /** + * Sets the relative volume of the current TV input session to handle the change of audio + * focus by setting. + * + * @param volume Volume scale from 0.0 to 1.0. + */ + public abstract void onSetVolume(float volume); + + /** + * Tunes to a given channel. + * + * @param channelUri The URI of the channel. + * @return {@code true} the tuning was successful, {@code false} otherwise. + */ + public abstract boolean onTune(Uri channelUri); + } + + /** + * Internal implementation of {@link TvInputSession}. This takes care of basic maintenance of + * the TV input session but most behavior must be implemented in {@link TvInputSessionImpl} + * returned by {@link TvInputService#onCreateSession}. + */ + private static class TvInputSessionImplInternal extends TvInputSession { + private final TvInputSessionImpl mSessionImpl; + + public TvInputSessionImplInternal(TvInputSessionImpl sessionImpl) { + mSessionImpl = sessionImpl; + } + + /** + * This method is called when the application would like to stop using the current input + * session. + */ + @Override + public final void release() { + mSessionImpl.onRelease(); + } + + /** + * Calls {@link TvInputSessionImpl#onSetSurface}. + */ + @Override + public final void setSurface(Surface surface) { + mSessionImpl.onSetSurface(surface); + // TODO: Handle failure. + } + + /** + * Calls {@link TvInputSessionImpl#onSetVolume}. + */ + @Override + public final void setVolume(float volume) { + mSessionImpl.onSetVolume(volume); + } + + /** + * Calls {@link TvInputSessionImpl#onTune}. + */ + @Override + public final void tune(Uri channelUri) { + mSessionImpl.onTune(channelUri); + // TODO: Handle failure. + } + } + + private final class ServiceHandler extends Handler { + private static final int DO_CREATE_SESSION = 1; + private static final int DO_BROADCAST_AVAILABILITY_CHANGE = 2; + + @Override + public final void handleMessage(Message msg) { + switch (msg.what) { + case DO_CREATE_SESSION: { + ITvInputSessionCallback cb = (ITvInputSessionCallback) msg.obj; + try { + TvInputSessionImpl sessionImpl = onCreateSession(); + if (sessionImpl == null) { + // Failed to create a session. + cb.onSessionCreated(null); + return; + } + ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this, + new TvInputSessionImplInternal(sessionImpl)); + cb.onSessionCreated(stub); + } catch (RemoteException e) { + Log.e(TAG, "error in onSessionCreated"); + } + return; + } + case DO_BROADCAST_AVAILABILITY_CHANGE: { + boolean isAvailable = (Boolean) msg.obj; + int n = mCallbacks.beginBroadcast(); + try { + for (int i = 0; i < n; i++) { + mCallbacks.getBroadcastItem(i).onAvailabilityChanged(mComponentName, + isAvailable); + } + } catch (RemoteException e) { + Log.e(TAG, "Unexpected exception", e); + } finally { + mCallbacks.finishBroadcast(); + } + return; + } + default: { + Log.w(TAG, "Unhandled message code: " + msg.what); + return; + } + } + } + } +} diff --git a/core/java/android/tv/TvInputSession.java b/core/java/android/tv/TvInputSession.java new file mode 100644 index 0000000..5b70a0b --- /dev/null +++ b/core/java/android/tv/TvInputSession.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2014 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 android.tv; + +import android.net.Uri; +import android.view.Surface; + +/** + * The TvInputSession provides the per-session functionality of TvInputService. + * + * @hide + */ +public abstract class TvInputSession { + /** + * This method is called when the application would like to stop using the current input + * session. + */ + public void release() { } + + /** + * Sets the {@link Surface} for the current input session on which the TV input renders video. + * + * @param surface {@link Surface} to be used for the video playback of this session. + */ + public void setSurface(Surface surface) { } + + /** + * This method is called when the application needs to handle the change of audio focus by + * setting the relative volume of the current TV input service session. + * + * @param volume Volume scale from 0.0 to 1.0. + */ + // TODO: Remove this once it becomes irrelevant for applications to handle audio focus. The plan + // is to introduce some new concepts that will solve a number of problems in audio policy today. + public void setVolume(float volume) { } + + /** + * Tunes to a given channel. + * + * @param channelUri The URI of the channel. + */ + public void tune(Uri channelUri) { } +} |