1487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk/*
2487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk * Copyright (C) 2012 The Android Open Source Project
3487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk *
4487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk * Licensed under the Apache License, Version 2.0 (the "License");
5487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk * you may not use this file except in compliance with the License.
6487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk * You may obtain a copy of the License at
7487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk *
8487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk *      http://www.apache.org/licenses/LICENSE-2.0
9487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk *
10487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk * Unless required by applicable law or agreed to in writing, software
11487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk * distributed under the License is distributed on an "AS IS" BASIS,
12487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk * See the License for the specific language governing permissions and
14487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk * limitations under the License.
15487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk */
16487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk
17487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsukpackage android.drm;
18487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk
19487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsukimport static android.drm.DrmConvertedStatus.STATUS_OK;
20487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsukimport static android.drm.DrmManagerClient.INVALID_SESSION;
21487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsukimport static android.system.OsConstants.SEEK_SET;
22487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk
23487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsukimport android.os.ParcelFileDescriptor;
24487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsukimport android.system.ErrnoException;
25487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsukimport android.system.Os;
26487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsukimport android.util.Log;
27487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk
28487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsukimport libcore.io.IoBridge;
29487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsukimport libcore.io.Streams;
30487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk
31487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsukimport java.io.FileDescriptor;
32487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsukimport java.io.FilterOutputStream;
33487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsukimport java.io.IOException;
34487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsukimport java.io.OutputStream;
35487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsukimport java.net.UnknownServiceException;
36487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsukimport java.util.Arrays;
37487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk
38487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk/**
39487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk * Stream that applies a {@link DrmManagerClient} transformation to data before
40487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk * writing to disk, similar to a {@link FilterOutputStream}.
41487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk *
42487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk * @hide
43487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk */
44487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsukpublic class DrmOutputStream extends OutputStream {
45487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk    private static final String TAG = "DrmOutputStream";
46487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk
47487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk    private final DrmManagerClient mClient;
48487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk    private final ParcelFileDescriptor mPfd;
49487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk    private final FileDescriptor mFd;
50487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk
51487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk    private int mSessionId = INVALID_SESSION;
52487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk
53487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk    /**
54487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk     * @param pfd Opened with "rw" mode.
55487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk     */
56487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk    public DrmOutputStream(DrmManagerClient client, ParcelFileDescriptor pfd, String mimeType)
57487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk            throws IOException {
58487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        mClient = client;
59487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        mPfd = pfd;
60487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        mFd = pfd.getFileDescriptor();
61487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk
62487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        mSessionId = mClient.openConvertSession(mimeType);
63487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        if (mSessionId == INVALID_SESSION) {
64487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk            throw new UnknownServiceException("Failed to open DRM session for " + mimeType);
65487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        }
66487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk    }
67487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk
68487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk    public void finish() throws IOException {
69487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        final DrmConvertedStatus status = mClient.closeConvertSession(mSessionId);
70487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        if (status.statusCode == STATUS_OK) {
71487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk            try {
72487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk                Os.lseek(mFd, status.offset, SEEK_SET);
73487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk            } catch (ErrnoException e) {
74487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk                e.rethrowAsIOException();
75487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk            }
76487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk            IoBridge.write(mFd, status.convertedData, 0, status.convertedData.length);
77487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk            mSessionId = INVALID_SESSION;
78487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        } else {
79487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk            throw new IOException("Unexpected DRM status: " + status.statusCode);
80487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        }
81487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk    }
82487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk
83487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk    @Override
84487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk    public void close() throws IOException {
85487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        if (mSessionId == INVALID_SESSION) {
86487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk            Log.w(TAG, "Closing stream without finishing");
87487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        }
88487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk
89487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        mPfd.close();
90487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk    }
91487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk
92487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk    @Override
93487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk    public void write(byte[] buffer, int offset, int count) throws IOException {
94487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        Arrays.checkOffsetAndCount(buffer.length, offset, count);
95487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk
96487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        final byte[] exactBuffer;
97487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        if (count == buffer.length) {
98487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk            exactBuffer = buffer;
99487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        } else {
100487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk            exactBuffer = new byte[count];
101487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk            System.arraycopy(buffer, offset, exactBuffer, 0, count);
102487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        }
103487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk
104487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        final DrmConvertedStatus status = mClient.convertData(mSessionId, exactBuffer);
105487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        if (status.statusCode == STATUS_OK) {
106487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk            IoBridge.write(mFd, status.convertedData, 0, status.convertedData.length);
107487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        } else {
108487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk            throw new IOException("Unexpected DRM status: " + status.statusCode);
109487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        }
110487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk    }
111487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk
112487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk    @Override
113487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk    public void write(int oneByte) throws IOException {
114487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk        Streams.writeSingleByte(this, oneByte);
115487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk    }
116487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk}
117487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8Rob Tsuk