1/*
2 * Copyright (C) 2012 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.drm;
18
19import static android.drm.DrmConvertedStatus.STATUS_OK;
20import static android.drm.DrmManagerClient.INVALID_SESSION;
21import static android.system.OsConstants.SEEK_SET;
22
23import android.os.ParcelFileDescriptor;
24import android.system.ErrnoException;
25import android.system.Os;
26import android.util.Log;
27
28import libcore.io.IoBridge;
29import libcore.io.Streams;
30
31import java.io.FileDescriptor;
32import java.io.FilterOutputStream;
33import java.io.IOException;
34import java.io.OutputStream;
35import java.net.UnknownServiceException;
36import java.util.Arrays;
37
38/**
39 * Stream that applies a {@link DrmManagerClient} transformation to data before
40 * writing to disk, similar to a {@link FilterOutputStream}.
41 *
42 * @hide
43 */
44public class DrmOutputStream extends OutputStream {
45    private static final String TAG = "DrmOutputStream";
46
47    private final DrmManagerClient mClient;
48    private final ParcelFileDescriptor mPfd;
49    private final FileDescriptor mFd;
50
51    private int mSessionId = INVALID_SESSION;
52
53    /**
54     * @param pfd Opened with "rw" mode.
55     */
56    public DrmOutputStream(DrmManagerClient client, ParcelFileDescriptor pfd, String mimeType)
57            throws IOException {
58        mClient = client;
59        mPfd = pfd;
60        mFd = pfd.getFileDescriptor();
61
62        mSessionId = mClient.openConvertSession(mimeType);
63        if (mSessionId == INVALID_SESSION) {
64            throw new UnknownServiceException("Failed to open DRM session for " + mimeType);
65        }
66    }
67
68    public void finish() throws IOException {
69        final DrmConvertedStatus status = mClient.closeConvertSession(mSessionId);
70        if (status.statusCode == STATUS_OK) {
71            try {
72                Os.lseek(mFd, status.offset, SEEK_SET);
73            } catch (ErrnoException e) {
74                e.rethrowAsIOException();
75            }
76            IoBridge.write(mFd, status.convertedData, 0, status.convertedData.length);
77            mSessionId = INVALID_SESSION;
78        } else {
79            throw new IOException("Unexpected DRM status: " + status.statusCode);
80        }
81    }
82
83    @Override
84    public void close() throws IOException {
85        if (mSessionId == INVALID_SESSION) {
86            Log.w(TAG, "Closing stream without finishing");
87        }
88
89        mPfd.close();
90    }
91
92    @Override
93    public void write(byte[] buffer, int offset, int count) throws IOException {
94        Arrays.checkOffsetAndCount(buffer.length, offset, count);
95
96        final byte[] exactBuffer;
97        if (count == buffer.length) {
98            exactBuffer = buffer;
99        } else {
100            exactBuffer = new byte[count];
101            System.arraycopy(buffer, offset, exactBuffer, 0, count);
102        }
103
104        final DrmConvertedStatus status = mClient.convertData(mSessionId, exactBuffer);
105        if (status.statusCode == STATUS_OK) {
106            IoBridge.write(mFd, status.convertedData, 0, status.convertedData.length);
107        } else {
108            throw new IOException("Unexpected DRM status: " + status.statusCode);
109        }
110    }
111
112    @Override
113    public void write(int oneByte) throws IOException {
114        Streams.writeSingleByte(this, oneByte);
115    }
116}
117