1/*
2 * Copyright (C) 2017 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
17package android.media;
18
19import android.annotation.NonNull;
20import android.media.MediaCasException.UnsupportedCasException;
21import android.os.IBinder;
22import android.os.Parcel;
23import android.os.Parcelable;
24import android.os.RemoteException;
25import android.os.ServiceSpecificException;
26import android.util.Log;
27
28import java.nio.ByteBuffer;
29
30/**
31 * MediaDescrambler class can be used in conjunction with {@link android.media.MediaCodec}
32 * and {@link android.media.MediaExtractor} to decode media data scrambled by conditional
33 * access (CA) systems such as those in the ISO/IEC13818-1.
34 *
35 * A MediaDescrambler object is initialized from a session opened by a MediaCas object,
36 * and can be used to descramble media streams scrambled with that session's keys.
37 *
38 * Scrambling schemes are identified by 16-bit unsigned integer as in CA_system_id.
39 *
40 */
41public final class MediaDescrambler implements AutoCloseable {
42    private static final String TAG = "MediaDescrambler";
43    private IDescrambler mIDescrambler;
44
45    private final void validateInternalStates() {
46        if (mIDescrambler == null) {
47            throw new IllegalStateException();
48        }
49    }
50
51    private final void cleanupAndRethrowIllegalState() {
52        mIDescrambler = null;
53        throw new IllegalStateException();
54    }
55
56    /**
57     * Class for parceling descrambling parameters over IDescrambler binder.
58     */
59    // This class currently is not used by Java binder. descramble() goes through
60    // jni to use shared memory. However, the parcelable is still required for AIDL.
61    static class DescrambleInfo implements Parcelable {
62        private DescrambleInfo() {
63        }
64
65        private DescrambleInfo(Parcel in) {
66        }
67
68        @Override
69        public int describeContents() {
70            return 0;
71        }
72
73        @Override
74        public void writeToParcel(Parcel dest, int flags) {
75        }
76
77        public static final Parcelable.Creator<DescrambleInfo> CREATOR
78                = new Parcelable.Creator<DescrambleInfo>() {
79            public DescrambleInfo createFromParcel(Parcel in) {
80                return new DescrambleInfo(in);
81            }
82
83            public DescrambleInfo[] newArray(int size) {
84                return new DescrambleInfo[size];
85            }
86        };
87    }
88
89    /**
90     * Instantiate a MediaDescrambler.
91     *
92     * @param CA_system_id The system id of the scrambling scheme.
93     *
94     * @throws UnsupportedCasException if the scrambling scheme is not supported.
95     */
96    public MediaDescrambler(int CA_system_id) throws UnsupportedCasException {
97        try {
98            mIDescrambler = MediaCas.getService().createDescrambler(CA_system_id);
99        } catch(Exception e) {
100            Log.e(TAG, "Failed to create descrambler: " + e);
101            mIDescrambler = null;
102        } finally {
103            if (mIDescrambler == null) {
104                throw new UnsupportedCasException("Unsupported CA_system_id " + CA_system_id);
105            }
106        }
107        native_setup(mIDescrambler.asBinder());
108    }
109
110    IBinder getBinder() {
111        validateInternalStates();
112
113        return mIDescrambler.asBinder();
114    }
115
116    /**
117     * Query if the scrambling scheme requires the use of a secure decoder
118     * to decode data of the given mime type.
119     *
120     * @param mime The mime type of the media data
121     *
122     * @throws IllegalStateException if the descrambler instance is not valid.
123     */
124    public final boolean requiresSecureDecoderComponent(@NonNull String mime) {
125        validateInternalStates();
126
127        try {
128            return mIDescrambler.requiresSecureDecoderComponent(mime);
129        } catch (RemoteException e) {
130            cleanupAndRethrowIllegalState();
131        }
132        return true;
133    }
134
135    /**
136     * Associate a MediaCas session with this MediaDescrambler instance.
137     * The MediaCas session is used to securely load decryption keys for
138     * the descrambler. The crypto keys loaded through the MediaCas session
139     * may be selected for use during the descrambling operation performed
140     * by {@link android.media.MediaExtractor or @link
141     * android.media.MediaCodec#queueSecureInputBuffer} by specifying even
142     * or odd key in the {@link android.media.MediaCodec.CryptoInfo#key} field.
143     *
144     * @param session the MediaCas session to associate with this
145     * MediaDescrambler instance.
146     *
147     * @throws IllegalStateException if the descrambler instance is not valid.
148     * @throws MediaCasStateException for CAS-specific state exceptions.
149     */
150    public final void setMediaCasSession(@NonNull MediaCas.Session session) {
151        validateInternalStates();
152
153        try {
154            mIDescrambler.setMediaCasSession(session.mSessionId);
155        } catch (ServiceSpecificException e) {
156            MediaCasStateException.throwExceptions(e);
157        } catch (RemoteException e) {
158            cleanupAndRethrowIllegalState();
159        }
160    }
161
162    /**
163     * Descramble a ByteBuffer of data described by a
164     * {@link android.media.MediaCodec.CryptoInfo} structure.
165     *
166     * @param srcBuf ByteBuffer containing the scrambled data, which starts at
167     * srcBuf.position().
168     * @param dstBuf ByteBuffer to hold the descrambled data, which starts at
169     * dstBuf.position().
170     * @param cryptoInfo a {@link android.media.MediaCodec.CryptoInfo} structure
171     * describing the subsamples contained in src.
172     *
173     * @return number of bytes that have been successfully descrambled, with negative
174     * values indicating errors.
175     *
176     * @throws IllegalStateException if the descrambler instance is not valid.
177     * @throws MediaCasStateException for CAS-specific state exceptions.
178     */
179    public final int descramble(
180            @NonNull ByteBuffer srcBuf, @NonNull ByteBuffer dstBuf,
181            @NonNull MediaCodec.CryptoInfo cryptoInfo) {
182        validateInternalStates();
183
184        if (cryptoInfo.numSubSamples <= 0) {
185            throw new IllegalArgumentException(
186                    "Invalid CryptoInfo: invalid numSubSamples=" + cryptoInfo.numSubSamples);
187        } else if (cryptoInfo.numBytesOfClearData == null
188                && cryptoInfo.numBytesOfEncryptedData == null) {
189            throw new IllegalArgumentException(
190                    "Invalid CryptoInfo: clearData and encryptedData size arrays are both null!");
191        } else if (cryptoInfo.numBytesOfClearData != null
192                && cryptoInfo.numBytesOfClearData.length < cryptoInfo.numSubSamples) {
193            throw new IllegalArgumentException(
194                    "Invalid CryptoInfo: numBytesOfClearData is too small!");
195        } else if (cryptoInfo.numBytesOfEncryptedData != null
196                && cryptoInfo.numBytesOfEncryptedData.length < cryptoInfo.numSubSamples) {
197            throw new IllegalArgumentException(
198                    "Invalid CryptoInfo: numBytesOfEncryptedData is too small!");
199        } else if (cryptoInfo.key == null || cryptoInfo.key.length != 16) {
200            throw new IllegalArgumentException(
201                    "Invalid CryptoInfo: key array is invalid!");
202        }
203
204        try {
205            return native_descramble(
206                    cryptoInfo.key[0],
207                    cryptoInfo.numSubSamples,
208                    cryptoInfo.numBytesOfClearData,
209                    cryptoInfo.numBytesOfEncryptedData,
210                    srcBuf, srcBuf.position(), srcBuf.limit(),
211                    dstBuf, dstBuf.position(), dstBuf.limit());
212        } catch (ServiceSpecificException e) {
213            MediaCasStateException.throwExceptions(e);
214        }
215        return -1;
216    }
217
218    @Override
219    public void close() {
220        if (mIDescrambler != null) {
221            try {
222                mIDescrambler.release();
223            } catch (RemoteException e) {
224            } finally {
225                mIDescrambler = null;
226            }
227        }
228        native_release();
229    }
230
231    @Override
232    protected void finalize() {
233        close();
234    }
235
236    private static native final void native_init();
237    private native final void native_setup(@NonNull IBinder decramblerBinder);
238    private native final void native_release();
239    private native final int native_descramble(
240            byte key, int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData,
241            @NonNull ByteBuffer srcBuf, int srcOffset, int srcLimit,
242            ByteBuffer dstBuf, int dstOffset, int dstLimit);
243
244    static {
245        System.loadLibrary("media_jni");
246        native_init();
247    }
248
249    private long mNativeContext;
250}