1/*
2 * Copyright 2018 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 androidx.media.subtitle;
18
19import android.content.Context;
20import android.util.AttributeSet;
21import android.view.View;
22import android.view.ViewGroup;
23import android.view.accessibility.CaptioningManager;
24import android.view.accessibility.CaptioningManager.CaptionStyle;
25import android.view.accessibility.CaptioningManager.CaptioningChangeListener;
26
27import androidx.annotation.RequiresApi;
28
29/**
30 * Abstract widget class to render a closed caption track.
31 */
32@RequiresApi(28)
33abstract class ClosedCaptionWidget extends ViewGroup implements SubtitleTrack.RenderingWidget {
34
35    interface ClosedCaptionLayout {
36        void setCaptionStyle(CaptionStyle captionStyle);
37        void setFontScale(float scale);
38    }
39
40    /** Captioning manager, used to obtain and track caption properties. */
41    private final CaptioningManager mManager;
42
43    /** Current caption style. */
44    protected CaptionStyle mCaptionStyle;
45
46    /** Callback for rendering changes. */
47    protected OnChangedListener mListener;
48
49    /** Concrete layout of CC. */
50    protected ClosedCaptionLayout mClosedCaptionLayout;
51
52    /** Whether a caption style change listener is registered. */
53    private boolean mHasChangeListener;
54
55    ClosedCaptionWidget(Context context) {
56        this(context, null);
57    }
58
59    ClosedCaptionWidget(Context context, AttributeSet attrs) {
60        this(context, attrs, 0);
61    }
62
63    ClosedCaptionWidget(Context context, AttributeSet attrs, int defStyle) {
64        this(context, attrs, defStyle, 0);
65    }
66
67    ClosedCaptionWidget(Context context, AttributeSet attrs, int defStyleAttr,
68            int defStyleRes) {
69        super(context, attrs, defStyleAttr, defStyleRes);
70
71        // Cannot render text over video when layer type is hardware.
72        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
73
74        mManager = (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
75        mCaptionStyle = mManager.getUserStyle();
76
77        mClosedCaptionLayout = createCaptionLayout(context);
78        mClosedCaptionLayout.setCaptionStyle(mCaptionStyle);
79        mClosedCaptionLayout.setFontScale(mManager.getFontScale());
80        addView((ViewGroup) mClosedCaptionLayout, LayoutParams.MATCH_PARENT,
81                LayoutParams.MATCH_PARENT);
82
83        requestLayout();
84    }
85
86    public abstract ClosedCaptionLayout createCaptionLayout(Context context);
87
88    @Override
89    public void setOnChangedListener(OnChangedListener listener) {
90        mListener = listener;
91    }
92
93    @Override
94    public void setSize(int width, int height) {
95        final int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
96        final int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
97
98        measure(widthSpec, heightSpec);
99        layout(0, 0, width, height);
100    }
101
102    @Override
103    public void setVisible(boolean visible) {
104        if (visible) {
105            setVisibility(View.VISIBLE);
106        } else {
107            setVisibility(View.GONE);
108        }
109
110        manageChangeListener();
111    }
112
113    @Override
114    public void onAttachedToWindow() {
115        super.onAttachedToWindow();
116
117        manageChangeListener();
118    }
119
120    @Override
121    public void onDetachedFromWindow() {
122        super.onDetachedFromWindow();
123
124        manageChangeListener();
125    }
126
127    @Override
128    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
129        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
130        ((ViewGroup) mClosedCaptionLayout).measure(widthMeasureSpec, heightMeasureSpec);
131    }
132
133    @Override
134    protected void onLayout(boolean changed, int l, int t, int r, int b) {
135        ((ViewGroup) mClosedCaptionLayout).layout(l, t, r, b);
136    }
137
138    /**
139     * Manages whether this renderer is listening for caption style changes.
140     */
141    private final CaptioningChangeListener mCaptioningListener = new CaptioningChangeListener() {
142        @Override
143        public void onUserStyleChanged(CaptionStyle userStyle) {
144            mCaptionStyle = userStyle;
145            mClosedCaptionLayout.setCaptionStyle(mCaptionStyle);
146        }
147
148        @Override
149        public void onFontScaleChanged(float fontScale) {
150            mClosedCaptionLayout.setFontScale(fontScale);
151        }
152    };
153
154    private void manageChangeListener() {
155        final boolean needsListener = isAttachedToWindow() && getVisibility() == View.VISIBLE;
156        if (mHasChangeListener != needsListener) {
157            mHasChangeListener = needsListener;
158
159            if (needsListener) {
160                mManager.addCaptioningChangeListener(mCaptioningListener);
161            } else {
162                mManager.removeCaptioningChangeListener(mCaptioningListener);
163            }
164        }
165    }
166}
167
168