15c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)/*
25c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * Copyright (C) 2012 The Android Open Source Project
35c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) *
45c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * Licensed under the Apache License, Version 2.0 (the "License");
55c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * you may not use this file except in compliance with the License.
65c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * You may obtain a copy of the License at
75c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) *
85c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) *      http://www.apache.org/licenses/LICENSE-2.0
95c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) *
105c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * Unless required by applicable law or agreed to in writing, software
115c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * distributed under the License is distributed on an "AS IS" BASIS,
125c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * See the License for the specific language governing permissions and
145c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) * limitations under the License.
155c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles) */
165c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
175c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)package android.drm;
185c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)
195c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)import static android.drm.DrmConvertedStatus.STATUS_OK;
205c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)import static android.drm.DrmManagerClient.INVALID_SESSION;
2153e740f4a82e17f3ae59772501622dc354e42336Torne (Richard Coles)import static android.system.OsConstants.SEEK_SET;
22d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles)
235c87bf8b86a7c82ef50fb7a89697d8e02e2553beTorne (Richard Coles)import 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