HdmiControlService.java revision d4a94db1cd44a536d535de890a0a14919a39a0dc
1dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project/*
2168021c2827312e17a13d77b54f7d030a08b257bMark Salyzyn * Copyright (C) 2014 The Android Open Source Project
3dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project *
4dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License");
5dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project * you may not use this file except in compliance with the License.
6dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project * You may obtain a copy of the License at
7dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project *
8dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project *      http://www.apache.org/licenses/LICENSE-2.0
9dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project *
10dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
11dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
12dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project * See the License for the specific language governing permissions and
14dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project * limitations under the License.
15dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project */
16dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
17dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectpackage com.android.server.hdmi;
18dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
19dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE;
20dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE;
21dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport static com.android.server.hdmi.Constants.DISABLED;
22b5a9890f900a5e2b53e91e6d63f3ade23240f90dKristian Monsenimport static com.android.server.hdmi.Constants.ENABLED;
23b5a9890f900a5e2b53e91e6d63f3ade23240f90dKristian Monsenimport static com.android.server.hdmi.Constants.OPTION_CEC_AUTO_WAKEUP;
24dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport static com.android.server.hdmi.Constants.OPTION_CEC_ENABLE;
25dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport static com.android.server.hdmi.Constants.OPTION_CEC_SERVICE_CONTROL;
26dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE;
27a04464adaf5b95ae953f8577632d3cf8aa2c80a3Mark Salyzynimport static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING;
28a04464adaf5b95ae953f8577632d3cf8aa2c80a3Mark Salyzynimport static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE;
29a04464adaf5b95ae953f8577632d3cf8aa2c80a3Mark Salyzyn
30a04464adaf5b95ae953f8577632d3cf8aa2c80a3Mark Salyzynimport android.annotation.Nullable;
31dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.content.BroadcastReceiver;
32dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.content.ContentResolver;
33dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.content.Context;
34dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.content.Intent;
35dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.content.IntentFilter;
36a04464adaf5b95ae953f8577632d3cf8aa2c80a3Mark Salyzynimport android.database.ContentObserver;
37a04464adaf5b95ae953f8577632d3cf8aa2c80a3Mark Salyzynimport android.hardware.hdmi.HdmiControlManager;
38a04464adaf5b95ae953f8577632d3cf8aa2c80a3Mark Salyzynimport android.hardware.hdmi.HdmiDeviceInfo;
39a04464adaf5b95ae953f8577632d3cf8aa2c80a3Mark Salyzynimport android.hardware.hdmi.HdmiHotplugEvent;
40dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.hardware.hdmi.HdmiPortInfo;
41dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.hardware.hdmi.IHdmiControlCallback;
42dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.hardware.hdmi.IHdmiControlService;
43dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.hardware.hdmi.IHdmiDeviceEventListener;
44dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.hardware.hdmi.IHdmiHotplugEventListener;
45dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.hardware.hdmi.IHdmiInputChangeListener;
46dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.hardware.hdmi.IHdmiMhlVendorCommandListener;
47dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.hardware.hdmi.IHdmiRecordListener;
48dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
49dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.hardware.hdmi.IHdmiVendorCommandListener;
50dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.media.AudioManager;
51dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.net.Uri;
52dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.os.Build;
53dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.os.Handler;
54dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.os.HandlerThread;
55dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.os.IBinder;
56dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.os.Looper;
57dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.os.PowerManager;
58dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.os.RemoteException;
59dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.os.SystemClock;
60dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.os.SystemProperties;
61dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.os.UserHandle;
62dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.provider.Settings.Global;
63dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.text.TextUtils;
64dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.util.ArraySet;
65dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.util.Slog;
66dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.util.SparseArray;
67dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport android.util.SparseIntArray;
68dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
69dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport com.android.internal.annotations.GuardedBy;
70dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport com.android.internal.util.IndentingPrintWriter;
71dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport com.android.server.SystemService;
72dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
73dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
74dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
75dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
76dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
77dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport libcore.util.EmptyArray;
78dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
79dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport java.io.FileDescriptor;
80dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport java.io.PrintWriter;
81dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport java.util.ArrayList;
82dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport java.util.Arrays;
83dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport java.util.Collections;
84dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport java.util.List;
85dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectimport java.util.Locale;
86dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
87dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project/**
88dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project * Provides a service for sending and processing HDMI control messages,
89dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project * HDMI-CEC and MHL control command, and providing the information on both standard.
90dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project */
91dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Projectpublic final class HdmiControlService extends SystemService {
92dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private static final String TAG = "HdmiControlService";
93dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
94dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    static final String PERMISSION = "android.permission.HDMI_CEC";
95dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
96dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // The reason code to initiate intializeCec().
97dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    static final int INITIATED_BY_ENABLE_CEC = 0;
98dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    static final int INITIATED_BY_BOOT_UP = 1;
99dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    static final int INITIATED_BY_SCREEN_ON = 2;
100dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    static final int INITIATED_BY_WAKE_UP_MESSAGE = 3;
101dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    static final int INITIATED_BY_HOTPLUG = 4;
102dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
103dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    /**
104dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * Interface to report send result.
105dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     */
106dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    interface SendMessageCallback {
107dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        /**
108dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project         * Called when {@link HdmiControlService#sendCecCommand} is completed.
109dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project         *
110dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project         * @param error result of send request.
111dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project         * <ul>
112dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project         * <li>{@link Constants#SEND_RESULT_SUCCESS}
113dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project         * <li>{@link Constants#SEND_RESULT_NAK}
114dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project         * <li>{@link Constants#SEND_RESULT_FAILURE}
115dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project         * </ul>
116dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project         */
117dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        void onSendCompleted(int error);
118dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
119dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
120dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    /**
121dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * Interface to get a list of available logical devices.
122dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     */
123dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    interface DevicePollingCallback {
124dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        /**
125dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project         * Called when device polling is finished.
126dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project         *
127dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project         * @param ackedAddress a list of logical addresses of available devices
128dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project         */
129dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        void onPollingFinished(List<Integer> ackedAddress);
130dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
131dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
132dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private class HdmiControlBroadcastReceiver extends BroadcastReceiver {
133dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        @ServiceThreadOnly
134dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        @Override
135dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        public void onReceive(Context context, Intent intent) {
136dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            assertRunOnServiceThread();
137dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            switch (intent.getAction()) {
138dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                case Intent.ACTION_SCREEN_OFF:
139dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    if (isPowerOnOrTransient()) {
140dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                        onStandby();
141dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    }
142dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    break;
143dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                case Intent.ACTION_SCREEN_ON:
144dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    if (isPowerStandbyOrTransient()) {
145dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                        onWakeUp();
146dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    }
147dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    break;
148dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                case Intent.ACTION_CONFIGURATION_CHANGED:
149dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    String language = Locale.getDefault().getISO3Language();
150dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    if (!mLanguage.equals(language)) {
151dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                        onLanguageChanged(language);
152dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    }
153dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    break;
154dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            }
155dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
156dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
157dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
158dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // A thread to handle synchronous IO of CEC and MHL control service.
159dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
160dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // and sparse call it shares a thread to handle IO operations.
161dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
162dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
163dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // Used to synchronize the access to the service.
164dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private final Object mLock = new Object();
165dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
166dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // Type of logical devices hosted in the system. Stored in the unmodifiable list.
167dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private final List<Integer> mLocalDevices;
168dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
169dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // List of records for hotplug event listener to handle the the caller killed in action.
170dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @GuardedBy("mLock")
171dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
172dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            new ArrayList<>();
173dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
174dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // List of records for device event listener to handle the caller killed in action.
175dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @GuardedBy("mLock")
176dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
177dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            new ArrayList<>();
178dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
179dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // List of records for vendor command listener to handle the caller killed in action.
180dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @GuardedBy("mLock")
181dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
182dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            new ArrayList<>();
183dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
184dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @GuardedBy("mLock")
185dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private InputChangeListenerRecord mInputChangeListenerRecord;
186dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
187dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @GuardedBy("mLock")
188dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private HdmiRecordListenerRecord mRecordListenerRecord;
189dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
190dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
191dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // handling will be disabled and no request will be handled.
192dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @GuardedBy("mLock")
193dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private boolean mHdmiControlEnabled;
194dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
195dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // Set to true while the service is in normal mode. While set to false, no input change is
196dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // allowed. Used for situations where input change can confuse users such as channel auto-scan,
197dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // system upgrade, etc., a.k.a. "prohibit mode".
198dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @GuardedBy("mLock")
199dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private boolean mProhibitMode;
200dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
201dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // List of records for system audio mode change to handle the the caller killed in action.
202dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private final ArrayList<SystemAudioModeChangeListenerRecord>
203dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            mSystemAudioModeChangeListenerRecords = new ArrayList<>();
204dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
205dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // Handler used to run a task in service thread.
206dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private final Handler mHandler = new Handler();
207dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
208dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private final SettingsObserver mSettingsObserver;
209dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
210dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private final HdmiControlBroadcastReceiver
211dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            mHdmiControlBroadcastReceiver = new HdmiControlBroadcastReceiver();
212dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
213dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @Nullable
214dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private HdmiCecController mCecController;
215dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
216dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // HDMI port information. Stored in the unmodifiable list to keep the static information
217dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // from being modified.
218dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private List<HdmiPortInfo> mPortInfo;
219dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
220dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // Map from path(physical address) to port ID.
221dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private UnmodifiableSparseIntArray mPortIdMap;
222dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
223dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // Map from port ID to HdmiPortInfo.
224dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
225dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
226dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // Map from port ID to HdmiDeviceInfo.
227dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap;
228dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
229dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private HdmiCecMessageValidator mMessageValidator;
230dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
231dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @ServiceThreadOnly
232dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
233dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
234dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @ServiceThreadOnly
235dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private String mLanguage = Locale.getDefault().getISO3Language();
236dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
237dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @ServiceThreadOnly
238dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private boolean mStandbyMessageReceived = false;
239dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
240dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @ServiceThreadOnly
241dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private boolean mWakeUpMessageReceived = false;
242dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
243dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @ServiceThreadOnly
244dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private int mActivePortId = Constants.INVALID_PORT_ID;
245dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
246dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // Set to true while the input change by MHL is allowed.
247dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @GuardedBy("mLock")
248dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private boolean mMhlInputChangeEnabled;
249dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
250dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // List of records for MHL Vendor command listener to handle the caller killed in action.
251dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @GuardedBy("mLock")
252dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private final ArrayList<HdmiMhlVendorCommandListenerRecord>
253dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            mMhlVendorCommandListenerRecords = new ArrayList<>();
254dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
255dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @GuardedBy("mLock")
256dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private List<HdmiDeviceInfo> mMhlDevices;
257dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
258dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @Nullable
259dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private HdmiMhlControllerStub mMhlController;
260dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
261dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // Last input port before switching to the MHL port. Should switch back to this port
262dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // when the mobile device sends the request one touch play with off.
263dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // Gets invalidated if we go to other port/input.
264dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @ServiceThreadOnly
265dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private int mLastInputMhl = Constants.INVALID_PORT_ID;
266dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
267dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    public HdmiControlService(Context context) {
268dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        super(context);
269dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        mLocalDevices = getIntList(SystemProperties.get(Constants.PROPERTY_DEVICE_TYPE));
270dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        mSettingsObserver = new SettingsObserver(mHandler);
271dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
272dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
273dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private static List<Integer> getIntList(String string) {
274dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        ArrayList<Integer> list = new ArrayList<>();
275dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
276dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        splitter.setString(string);
277dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        for (String item : splitter) {
278dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            try {
279dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                list.add(Integer.parseInt(item));
280dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            } catch (NumberFormatException e) {
281dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                Slog.w(TAG, "Can't parseInt: " + item);
282dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            }
283dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
284dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        return Collections.unmodifiableList(list);
285dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
286dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
287dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @Override
288dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    public void onStart() {
289dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        mIoThread.start();
290dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
291dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        mProhibitMode = false;
292dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
293dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
294dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
295dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        mCecController = HdmiCecController.create(this);
296dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        if (mCecController != null) {
297dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            // TODO: Remove this as soon as OEM's HAL implementation is corrected.
298dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            mCecController.setOption(OPTION_CEC_ENABLE, ENABLED);
299dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
300dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            // TODO: load value for mHdmiControlEnabled from preference.
301dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            if (mHdmiControlEnabled) {
302dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                initializeCec(INITIATED_BY_BOOT_UP);
303dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            }
304dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        } else {
305dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            Slog.i(TAG, "Device does not support HDMI-CEC.");
306dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
307dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
308dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        mMhlController = HdmiMhlControllerStub.create(this);
309dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        if (!mMhlController.isReady()) {
310dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            Slog.i(TAG, "Device does not support MHL-control.");
311dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
312dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        mMhlDevices = Collections.emptyList();
313dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
314dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        initPortInfo();
315dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        mMessageValidator = new HdmiCecMessageValidator(this);
316dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
317dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
318dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        // Register broadcast receiver for power state change.
319dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        if (mCecController != null) {
320dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            IntentFilter filter = new IntentFilter();
321dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            filter.addAction(Intent.ACTION_SCREEN_OFF);
322dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            filter.addAction(Intent.ACTION_SCREEN_ON);
323dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
324dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);
325dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
326dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
327dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
328dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    /**
329168021c2827312e17a13d77b54f7d030a08b257bMark Salyzyn     * Called when the initialization of local devices is complete.
330168021c2827312e17a13d77b54f7d030a08b257bMark Salyzyn     */
331dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private void onInitializeCecComplete() {
332168021c2827312e17a13d77b54f7d030a08b257bMark Salyzyn        if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
333168021c2827312e17a13d77b54f7d030a08b257bMark Salyzyn            mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
334dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
335dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        mWakeUpMessageReceived = false;
336dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
337dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        if (isTvDevice()) {
338dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            mCecController.setOption(OPTION_CEC_AUTO_WAKEUP, toInt(tv().getAutoWakeup()));
339dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            registerContentObserver();
340dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
341dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
342dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
343dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private void registerContentObserver() {
344dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        ContentResolver resolver = getContext().getContentResolver();
345dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        String[] settings = new String[] {
346dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                Global.HDMI_CONTROL_ENABLED,
347dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
348dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
349dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                Global.MHL_INPUT_SWITCHING_ENABLED,
350dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                Global.MHL_POWER_CHARGE_ENABLED
351dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        };
352dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        for (String s : settings) {
353dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
354dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    UserHandle.USER_ALL);
355dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
356dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
357dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
358dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private class SettingsObserver extends ContentObserver {
359dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        public SettingsObserver(Handler handler) {
360dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            super(handler);
361dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
362dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
363dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        // onChange is set up to run in service thread.
364dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        @Override
365dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        public void onChange(boolean selfChange, Uri uri) {
366dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            String option = uri.getLastPathSegment();
367dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            boolean enabled = readBooleanSetting(option, true);
368dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            switch (option) {
369dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                case Global.HDMI_CONTROL_ENABLED:
370dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    setControlEnabled(enabled);
371dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    break;
372dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
373dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    tv().setAutoWakeup(enabled);
374dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    setCecOption(OPTION_CEC_AUTO_WAKEUP, toInt(enabled));
375dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    break;
376dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
377dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    tv().setAutoDeviceOff(enabled);
378dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    // No need to propagate to HAL.
379dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    break;
380dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                case Global.MHL_INPUT_SWITCHING_ENABLED:
381dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    setMhlInputChangeEnabled(enabled);
382dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    break;
383dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                case Global.MHL_POWER_CHARGE_ENABLED:
384dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled));
385dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    break;
386dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            }
387dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
388dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
389dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
390dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private static int toInt(boolean enabled) {
391dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        return enabled ? ENABLED : DISABLED;
392dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
393dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
394dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    boolean readBooleanSetting(String key, boolean defVal) {
395dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        ContentResolver cr = getContext().getContentResolver();
396dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        return Global.getInt(cr, key, toInt(defVal)) == ENABLED;
397dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
398dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
399dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    void writeBooleanSetting(String key, boolean value) {
400dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        ContentResolver cr = getContext().getContentResolver();
401dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        Global.putInt(cr, key, toInt(value));
402dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
403dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
404dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private void unregisterSettingsObserver() {
405dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        getContext().getContentResolver().unregisterContentObserver(mSettingsObserver);
406dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
407d2c8f52189f8c2a13b88a107c6495f9d83196d2dAndrew Hsieh
408dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private void initializeCec(int initiatedBy) {
409dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, ENABLED);
410dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        initializeLocalDevices(initiatedBy);
411dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
412dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
413dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @ServiceThreadOnly
414dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private void initializeLocalDevices(final int initiatedBy) {
415dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        assertRunOnServiceThread();
416dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        // A container for [Device type, Local device info].
417dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
418dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        clearLocalDevices();
419dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        for (int type : mLocalDevices) {
420dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type);
421dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            localDevice.init();
422dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            localDevices.add(localDevice);
423dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
424dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        allocateLogicalAddress(localDevices, initiatedBy);
425dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
426d2c8f52189f8c2a13b88a107c6495f9d83196d2dAndrew Hsieh
427d2c8f52189f8c2a13b88a107c6495f9d83196d2dAndrew Hsieh    @ServiceThreadOnly
428dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private void allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices,
429dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            final int initiatedBy) {
430dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        assertRunOnServiceThread();
431dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        final ArrayList<HdmiCecLocalDevice> allocatedDevices = new ArrayList<>();
432dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        final int[] finished = new int[1];
433dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
434dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            mCecController.allocateLogicalAddress(localDevice.getType(),
435dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    localDevice.getPreferredAddress(), new AllocateAddressCallback() {
436dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                @Override
437dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                public void onAllocated(int deviceType, int logicalAddress) {
438dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    if (logicalAddress == Constants.ADDR_UNREGISTERED) {
439dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                        Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
440dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    } else {
441dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                        // Set POWER_STATUS_ON to all local devices because they share lifetime
442dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                        // with system.
443dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                        HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
444dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                                HdmiControlManager.POWER_STATUS_ON);
445dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                        localDevice.setDeviceInfo(deviceInfo);
446dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                        mCecController.addLocalDevice(deviceType, localDevice);
447dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                        mCecController.addLogicalAddress(logicalAddress);
448dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                        allocatedDevices.add(localDevice);
449dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    }
450dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
451dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    // Address allocation completed for all devices. Notify each device.
452dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    if (allocatingDevices.size() == ++finished[0]) {
453dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                        if (initiatedBy != INITIATED_BY_HOTPLUG) {
454dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                            // In case of the hotplug we don't call onInitializeCecComplete()
455dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                            // since we reallocate the logical address only.
456dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                            onInitializeCecComplete();
457dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                        }
458dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                        notifyAddressAllocated(allocatedDevices, initiatedBy);
459dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                    }
460dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                }
461dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            });
462dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
4632a7f2ae7d4b25f89e36be04e47b9e7a3d76e0cfdCarl Shapiro    }
464dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
465dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @ServiceThreadOnly
466dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
467dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        assertRunOnServiceThread();
468dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        for (HdmiCecLocalDevice device : devices) {
469dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            int address = device.getDeviceInfo().getLogicalAddress();
470dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            device.handleAddressAllocated(address, initiatedBy);
471dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
472dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
473dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
474dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
475dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    // keep them in one place.
476dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @ServiceThreadOnly
477dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private void initPortInfo() {
478dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        assertRunOnServiceThread();
479dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        HdmiPortInfo[] cecPortInfo = null;
480dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
481dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        // CEC HAL provides majority of the info while MHL does only MHL support flag for
482dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        // each port. Return empty array if CEC HAL didn't provide the info.
483dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        if (mCecController != null) {
484dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            cecPortInfo = mCecController.getPortInfos();
485dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
486dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        if (cecPortInfo == null) {
487dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            return;
488dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
489dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
490dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
491dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        SparseIntArray portIdMap = new SparseIntArray();
492dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
493dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        for (HdmiPortInfo info : cecPortInfo) {
494dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            portIdMap.put(info.getAddress(), info.getId());
495dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            portInfoMap.put(info.getId(), info);
496dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
497dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
498dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
499dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
500dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
501dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
502dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
503dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
504dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        for (HdmiPortInfo info : mhlPortInfo) {
505dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            if (info.isMhlSupported()) {
506dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                mhlSupportedPorts.add(info.getId());
507dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            }
508dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
509dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
510dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
511dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        // cec port info if we do not have have port that supports MHL.
512dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        if (mhlSupportedPorts.isEmpty()) {
513dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo));
514dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            return;
515dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
516dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
517dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        for (HdmiPortInfo info : cecPortInfo) {
518dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            if (mhlSupportedPorts.contains(info.getId())) {
519dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
520dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                        info.isCecSupported(), true, info.isArcSupported()));
521dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            } else {
522dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                result.add(info);
523dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            }
524dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
525dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        mPortInfo = Collections.unmodifiableList(result);
526dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
527dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
528dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    List<HdmiPortInfo> getPortInfo() {
529dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        return mPortInfo;
530dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
531dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
532dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    /**
533dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * Returns HDMI port information for the given port id.
534dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     *
535dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * @param portId HDMI port id
536dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * @return {@link HdmiPortInfo} for the given port
537dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     */
538dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    HdmiPortInfo getPortInfo(int portId) {
539dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        return mPortInfoMap.get(portId, null);
540dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
541dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
542dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    /**
543dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * Returns the routing path (physical address) of the HDMI port for the given
544dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * port id.
545dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     */
546dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    int portIdToPath(int portId) {
547dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        HdmiPortInfo portInfo = getPortInfo(portId);
548dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        if (portInfo == null) {
549dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            Slog.e(TAG, "Cannot find the port info: " + portId);
550dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            return Constants.INVALID_PHYSICAL_ADDRESS;
551dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
552dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        return portInfo.getAddress();
553dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
554dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
555dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    /**
556dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * Returns the id of HDMI port located at the top of the hierarchy of
557dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
558dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * the port id to be returned is the ID associated with the port address
559dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
560dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     */
561dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    int pathToPortId(int path) {
562dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
563dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
564dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
565dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
566dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    boolean isValidPortId(int portId) {
567dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        return getPortInfo(portId) != null;
568dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
569dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
570dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    /**
571dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * Returns {@link Looper} for IO operation.
572dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     *
573dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * <p>Declared as package-private.
574dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     */
575dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    Looper getIoLooper() {
576dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        return mIoThread.getLooper();
577dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
578dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
579dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    /**
580dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * Returns {@link Looper} of main thread. Use this {@link Looper} instance
581dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * for tasks that are running on main service thread.
582dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     *
583dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * <p>Declared as package-private.
584dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     */
585dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    Looper getServiceLooper() {
586dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        return mHandler.getLooper();
587dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
588dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
589dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    /**
590dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * Returns physical address of the device.
591dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     */
592dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    int getPhysicalAddress() {
593dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        return mCecController.getPhysicalAddress();
594dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
595dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
596dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    /**
597dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * Returns vendor id of CEC service.
598dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     */
599dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    int getVendorId() {
600dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        return mCecController.getVendorId();
601dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
602dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
603dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @ServiceThreadOnly
604dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
605dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        assertRunOnServiceThread();
606dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        HdmiCecLocalDeviceTv tv = tv();
607dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        if (tv == null) {
608dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            return null;
609dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
610dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        return tv.getCecDeviceInfo(logicalAddress);
611dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
612dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
613dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    /**
614dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * Returns version of CEC.
615dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     */
616dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    int getCecVersion() {
617dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        return mCecController.getVersion();
618dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
619dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
620a04464adaf5b95ae953f8577632d3cf8aa2c80a3Mark Salyzyn    /**
621dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * Whether a device of the specified physical address is connected to ARC enabled port.
622dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     */
623dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    boolean isConnectedToArcPort(int physicalAddress) {
624dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        int portId = pathToPortId(physicalAddress);
625dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        if (portId != Constants.INVALID_PORT_ID) {
626dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            return mPortInfoMap.get(portId).isArcSupported();
627dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
628dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        return false;
629dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
630dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
631dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    void runOnServiceThread(Runnable runnable) {
632dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        mHandler.post(runnable);
633dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
634dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
635dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
636dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        mHandler.postAtFrontOfQueue(runnable);
637dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
638dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
639dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    private void assertRunOnServiceThread() {
640dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        if (Looper.myLooper() != mHandler.getLooper()) {
641dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            throw new IllegalStateException("Should run on service thread.");
642dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
643dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
644dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
645dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    /**
646dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * Transmit a CEC command to CEC bus.
647dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     *
648dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * @param command CEC command to send out
649dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * @param callback interface used to the result of send command
650dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     */
651dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @ServiceThreadOnly
652dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
653dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        assertRunOnServiceThread();
654dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) {
655dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            mCecController.sendCommand(command, callback);
656dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        } else {
657dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            HdmiLogger.error("Invalid message type:" + command);
658dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            if (callback != null) {
659dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                callback.onSendCompleted(Constants.SEND_RESULT_FAILURE);
660dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            }
661dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        }
662dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
663dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
664dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @ServiceThreadOnly
665dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    void sendCecCommand(HdmiCecMessage command) {
666dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        assertRunOnServiceThread();
667dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        sendCecCommand(command, null);
668dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
669dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
670dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    /**
671dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * Send <Feature Abort> command on the given CEC message if possible.
672dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * If the aborted message is invalid, then it wont send the message.
673dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * @param command original command to be aborted
674dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     * @param reason reason of feature abort
675dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project     */
676dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @ServiceThreadOnly
677dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
678dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        assertRunOnServiceThread();
679dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        mCecController.maySendFeatureAbortCommand(command, reason);
680dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    }
681dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project
682dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    @ServiceThreadOnly
683dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project    boolean handleCecCommand(HdmiCecMessage message) {
684dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        assertRunOnServiceThread();
685dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        int errorCode = mMessageValidator.isValid(message);
686dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project        if (errorCode != HdmiCecMessageValidator.OK) {
687dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            // We'll not response on the messages with the invalid source or destination.
688dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
689dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project                maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
690dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            }
691dd7bc3319deb2b77c5d07a51b7d6cd7e11b5beb0The Android Open Source Project            return true;
692        }
693        return dispatchMessageToLocalDevice(message);
694    }
695
696    void setAudioReturnChannel(boolean enabled) {
697        mCecController.setAudioReturnChannel(enabled);
698    }
699
700    @ServiceThreadOnly
701    private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
702        assertRunOnServiceThread();
703        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
704            if (device.dispatchMessage(message)
705                    && message.getDestination() != Constants.ADDR_BROADCAST) {
706                return true;
707            }
708        }
709
710        if (message.getDestination() != Constants.ADDR_BROADCAST) {
711            HdmiLogger.warning("Unhandled cec command:" + message);
712        }
713        return false;
714    }
715
716    /**
717     * Called when a new hotplug event is issued.
718     *
719     * @param portId hdmi port number where hot plug event issued.
720     * @param connected whether to be plugged in or not
721     */
722    @ServiceThreadOnly
723    void onHotplug(int portId, boolean connected) {
724        assertRunOnServiceThread();
725
726        ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
727        for (int type : mLocalDevices) {
728            if (type == HdmiDeviceInfo.DEVICE_TV) {
729                // Skip the reallocation of the logical address on TV.
730                continue;
731            }
732            HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
733            if (localDevice == null) {
734                localDevice = HdmiCecLocalDevice.create(this, type);
735                localDevice.init();
736            }
737            localDevices.add(localDevice);
738        }
739        allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
740
741        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
742            device.onHotplug(portId, connected);
743        }
744        announceHotplugEvent(portId, connected);
745    }
746
747    /**
748     * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
749     * devices.
750     *
751     * @param callback an interface used to get a list of all remote devices' address
752     * @param sourceAddress a logical address of source device where sends polling message
753     * @param pickStrategy strategy how to pick polling candidates
754     * @param retryCount the number of retry used to send polling message to remote devices
755     * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
756     */
757    @ServiceThreadOnly
758    void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
759            int retryCount) {
760        assertRunOnServiceThread();
761        mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
762                retryCount);
763    }
764
765    private int checkPollStrategy(int pickStrategy) {
766        int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
767        if (strategy == 0) {
768            throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
769        }
770        int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
771        if (iterationStrategy == 0) {
772            throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
773        }
774        return strategy | iterationStrategy;
775    }
776
777    List<HdmiCecLocalDevice> getAllLocalDevices() {
778        assertRunOnServiceThread();
779        return mCecController.getLocalDeviceList();
780    }
781
782    Object getServiceLock() {
783        return mLock;
784    }
785
786    void setAudioStatus(boolean mute, int volume) {
787        AudioManager audioManager = getAudioManager();
788        boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
789        if (mute) {
790            if (!muted) {
791                audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
792            }
793        } else {
794            if (muted) {
795                audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
796            }
797            // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
798            // volume change notification back to hdmi control service.
799            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
800                    AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
801        }
802    }
803
804    void announceSystemAudioModeChange(boolean enabled) {
805        synchronized (mLock) {
806            for (SystemAudioModeChangeListenerRecord record :
807                    mSystemAudioModeChangeListenerRecords) {
808                invokeSystemAudioModeChangeLocked(record.mListener, enabled);
809            }
810        }
811    }
812
813    private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
814        // TODO: find better name instead of model name.
815        String displayName = Build.MODEL;
816        return new HdmiDeviceInfo(logicalAddress,
817                getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
818                getVendorId(), displayName);
819    }
820
821    @ServiceThreadOnly
822    void sendMhlSubcommand(int portId, HdmiMhlSubcommand command) {
823        assertRunOnServiceThread();
824        sendMhlSubcommand(portId, command, null);
825    }
826
827    @ServiceThreadOnly
828    void sendMhlSubcommand(int portId, HdmiMhlSubcommand command, SendMessageCallback callback) {
829        assertRunOnServiceThread();
830        mMhlController.sendSubcommand(portId, command, callback);
831    }
832
833    @ServiceThreadOnly
834    boolean handleMhlSubcommand(int portId, HdmiMhlSubcommand message) {
835        assertRunOnServiceThread();
836
837        HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId);
838        if (device != null) {
839            return device.handleSubcommand(message);
840        }
841        Slog.w(TAG, "No mhl device exists[portId:" + portId + ", message:" + message);
842        return false;
843    }
844
845    @ServiceThreadOnly
846    void handleMhlHotplugEvent(int portId, boolean connected) {
847        assertRunOnServiceThread();
848        if (connected) {
849            HdmiMhlLocalDevice newDevice = new HdmiMhlLocalDevice(this, portId);
850            HdmiMhlLocalDevice oldDevice = mMhlController.addLocalDevice(newDevice);
851            if (oldDevice != null) {
852                oldDevice.onDeviceRemoved();
853                Slog.i(TAG, "Old device of port " + portId + " is removed");
854            }
855        } else {
856            HdmiMhlLocalDevice device = mMhlController.removeLocalDevice(portId);
857            if (device != null) {
858                device.onDeviceRemoved();
859                // There is no explicit event for device removal unlike capability register event
860                // used for device addition . Hence we remove the device on hotplug event.
861                HdmiDeviceInfo deviceInfo = device.getInfo();
862                if (deviceInfo != null) {
863                    invokeDeviceEventListeners(deviceInfo, DEVICE_EVENT_REMOVE_DEVICE);
864                    updateSafeMhlInput();
865                }
866            } else {
867                Slog.w(TAG, "No device to remove:[portId=" + portId);
868            }
869        }
870        announceHotplugEvent(portId, connected);
871    }
872
873    @ServiceThreadOnly
874    void handleMhlCbusModeChanged(int portId, int cbusmode) {
875        assertRunOnServiceThread();
876        HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId);
877        if (device != null) {
878            device.setCbusMode(cbusmode);
879        } else {
880            Slog.w(TAG, "No mhl device exists for cbus mode change[portId:" + portId +
881                    ", cbusmode:" + cbusmode + "]");
882        }
883    }
884
885    @ServiceThreadOnly
886    void handleMhlVbusOvercurrent(int portId, boolean on) {
887        assertRunOnServiceThread();
888        HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId);
889        if (device != null) {
890            device.onVbusOvercurrentDetected(on);
891        } else {
892            Slog.w(TAG, "No mhl device exists for vbus overcurrent event[portId:" + portId + "]");
893        }
894    }
895
896    @ServiceThreadOnly
897    void handleMhlCapabilityRegisterChanged(int portId, int adopterId, int deviceId) {
898        assertRunOnServiceThread();
899        HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId);
900
901        // Hotplug event should already have been called before capability register change event.
902        if (device != null) {
903            device.setCapabilityRegister(adopterId, deviceId);
904            invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_ADD_DEVICE);
905            updateSafeMhlInput();
906        } else {
907            Slog.w(TAG, "No mhl device exists for capability register change event[portId:"
908                    + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
909        }
910    }
911
912    @ServiceThreadOnly
913    private void updateSafeMhlInput() {
914        assertRunOnServiceThread();
915        List<HdmiDeviceInfo> inputs = Collections.emptyList();
916        SparseArray<HdmiMhlLocalDevice> devices = mMhlController.getAllLocalDevices();
917        for (int i = 0; i < devices.size(); ++i) {
918            HdmiMhlLocalDevice device = devices.valueAt(i);
919            HdmiDeviceInfo info = device.getInfo();
920            if (info != null) {
921                if (inputs.isEmpty()) {
922                    inputs = new ArrayList<>();
923                }
924                inputs.add(device.getInfo());
925            }
926        }
927        synchronized (mLock) {
928            mMhlDevices = inputs;
929        }
930    }
931
932    private List<HdmiDeviceInfo> getMhlDevicesLocked() {
933        return mMhlDevices;
934    }
935
936    private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient {
937        private final IHdmiMhlVendorCommandListener mListener;
938
939        public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) {
940            mListener = listener;
941        }
942
943        @Override
944        public void binderDied() {
945            mMhlVendorCommandListenerRecords.remove(this);
946        }
947    }
948
949    // Record class that monitors the event of the caller of being killed. Used to clean up
950    // the listener list and record list accordingly.
951    private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
952        private final IHdmiHotplugEventListener mListener;
953
954        public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
955            mListener = listener;
956        }
957
958        @Override
959        public void binderDied() {
960            synchronized (mLock) {
961                mHotplugEventListenerRecords.remove(this);
962            }
963        }
964    }
965
966    private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
967        private final IHdmiDeviceEventListener mListener;
968
969        public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
970            mListener = listener;
971        }
972
973        @Override
974        public void binderDied() {
975            synchronized (mLock) {
976                mDeviceEventListenerRecords.remove(this);
977            }
978        }
979    }
980
981    private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
982        private final IHdmiSystemAudioModeChangeListener mListener;
983
984        public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
985            mListener = listener;
986        }
987
988        @Override
989        public void binderDied() {
990            synchronized (mLock) {
991                mSystemAudioModeChangeListenerRecords.remove(this);
992            }
993        }
994    }
995
996    class VendorCommandListenerRecord implements IBinder.DeathRecipient {
997        private final IHdmiVendorCommandListener mListener;
998        private final int mDeviceType;
999
1000        public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
1001            mListener = listener;
1002            mDeviceType = deviceType;
1003        }
1004
1005        @Override
1006        public void binderDied() {
1007            synchronized (mLock) {
1008                mVendorCommandListenerRecords.remove(this);
1009            }
1010        }
1011    }
1012
1013    private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
1014        private final IHdmiRecordListener mListener;
1015
1016        public HdmiRecordListenerRecord(IHdmiRecordListener listener) {
1017            mListener = listener;
1018        }
1019
1020        @Override
1021        public void binderDied() {
1022            synchronized (mLock) {
1023                mRecordListenerRecord = null;
1024            }
1025        }
1026    }
1027
1028    private void enforceAccessPermission() {
1029        getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
1030    }
1031
1032    private final class BinderService extends IHdmiControlService.Stub {
1033        @Override
1034        public int[] getSupportedTypes() {
1035            enforceAccessPermission();
1036            // mLocalDevices is an unmodifiable list - no lock necesary.
1037            int[] localDevices = new int[mLocalDevices.size()];
1038            for (int i = 0; i < localDevices.length; ++i) {
1039                localDevices[i] = mLocalDevices.get(i);
1040            }
1041            return localDevices;
1042        }
1043
1044        @Override
1045        public HdmiDeviceInfo getActiveSource() {
1046            HdmiCecLocalDeviceTv tv = tv();
1047            if (tv == null) {
1048                Slog.w(TAG, "Local tv device not available");
1049                return null;
1050            }
1051            ActiveSource activeSource = tv.getActiveSource();
1052            if (activeSource.isValid()) {
1053                return new HdmiDeviceInfo(activeSource.logicalAddress,
1054                        activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
1055                        HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
1056            }
1057            int activePath = tv.getActivePath();
1058            if (activePath != HdmiDeviceInfo.PATH_INVALID) {
1059                return new HdmiDeviceInfo(activePath, tv.getActivePortId());
1060            }
1061            return null;
1062        }
1063
1064        @Override
1065        public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) {
1066            enforceAccessPermission();
1067            runOnServiceThread(new Runnable() {
1068                @Override
1069                public void run() {
1070                    if (callback == null) {
1071                        Slog.e(TAG, "Callback cannot be null");
1072                        return;
1073                    }
1074                    HdmiCecLocalDeviceTv tv = tv();
1075                    if (tv == null) {
1076                        Slog.w(TAG, "Local tv device not available");
1077                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1078                        return;
1079                    }
1080                    HdmiMhlLocalDevice device = mMhlController.getLocalDeviceById(deviceId);
1081                    if (device != null) {
1082                        if (device.getPortId() == tv.getActivePortId()) {
1083                            invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
1084                            return;
1085                        }
1086                        // Upon selecting MHL device, we send RAP[Content On] to wake up
1087                        // the connected mobile device, start routing control to switch ports.
1088                        // callback is handled by MHL action.
1089                        device.turnOn(callback);
1090                        tv.doManualPortSwitching(device.getPortId(), null);
1091                        return;
1092                    }
1093                    tv.deviceSelect(deviceId, callback);
1094                }
1095            });
1096        }
1097
1098        @Override
1099        public void portSelect(final int portId, final IHdmiControlCallback callback) {
1100            enforceAccessPermission();
1101            runOnServiceThread(new Runnable() {
1102                @Override
1103                public void run() {
1104                    if (callback == null) {
1105                        Slog.e(TAG, "Callback cannot be null");
1106                        return;
1107                    }
1108                    HdmiCecLocalDeviceTv tv = tv();
1109                    if (tv == null) {
1110                        Slog.w(TAG, "Local tv device not available");
1111                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1112                        return;
1113                    }
1114                    tv.doManualPortSwitching(portId, callback);
1115                }
1116            });
1117        }
1118
1119        @Override
1120        public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
1121            enforceAccessPermission();
1122            runOnServiceThread(new Runnable() {
1123                @Override
1124                public void run() {
1125                    HdmiMhlLocalDevice device = mMhlController.getLocalDevice(mActivePortId);
1126                    if (device != null) {
1127                        device.sendKeyEvent(keyCode, isPressed);
1128                        return;
1129                    }
1130                    if (mCecController != null) {
1131                        HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1132                        if (localDevice == null) {
1133                            Slog.w(TAG, "Local device not available");
1134                            return;
1135                        }
1136                        localDevice.sendKeyEvent(keyCode, isPressed);
1137                    }
1138                }
1139            });
1140        }
1141
1142        @Override
1143        public void oneTouchPlay(final IHdmiControlCallback callback) {
1144            enforceAccessPermission();
1145            runOnServiceThread(new Runnable() {
1146                @Override
1147                public void run() {
1148                    HdmiControlService.this.oneTouchPlay(callback);
1149                }
1150            });
1151        }
1152
1153        @Override
1154        public void queryDisplayStatus(final IHdmiControlCallback callback) {
1155            enforceAccessPermission();
1156            runOnServiceThread(new Runnable() {
1157                @Override
1158                public void run() {
1159                    HdmiControlService.this.queryDisplayStatus(callback);
1160                }
1161            });
1162        }
1163
1164        @Override
1165        public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
1166            enforceAccessPermission();
1167            HdmiControlService.this.addHotplugEventListener(listener);
1168        }
1169
1170        @Override
1171        public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
1172            enforceAccessPermission();
1173            HdmiControlService.this.removeHotplugEventListener(listener);
1174        }
1175
1176        @Override
1177        public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
1178            enforceAccessPermission();
1179            HdmiControlService.this.addDeviceEventListener(listener);
1180        }
1181
1182        @Override
1183        public List<HdmiPortInfo> getPortInfo() {
1184            enforceAccessPermission();
1185            return HdmiControlService.this.getPortInfo();
1186        }
1187
1188        @Override
1189        public boolean canChangeSystemAudioMode() {
1190            enforceAccessPermission();
1191            HdmiCecLocalDeviceTv tv = tv();
1192            if (tv == null) {
1193                return false;
1194            }
1195            return tv.hasSystemAudioDevice();
1196        }
1197
1198        @Override
1199        public boolean getSystemAudioMode() {
1200            enforceAccessPermission();
1201            HdmiCecLocalDeviceTv tv = tv();
1202            if (tv == null) {
1203                return false;
1204            }
1205            return tv.isSystemAudioActivated();
1206        }
1207
1208        @Override
1209        public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
1210            enforceAccessPermission();
1211            runOnServiceThread(new Runnable() {
1212                @Override
1213                public void run() {
1214                    HdmiCecLocalDeviceTv tv = tv();
1215                    if (tv == null) {
1216                        Slog.w(TAG, "Local tv device not available");
1217                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1218                        return;
1219                    }
1220                    tv.changeSystemAudioMode(enabled, callback);
1221                }
1222            });
1223        }
1224
1225        @Override
1226        public void addSystemAudioModeChangeListener(
1227                final IHdmiSystemAudioModeChangeListener listener) {
1228            enforceAccessPermission();
1229            HdmiControlService.this.addSystemAudioModeChangeListner(listener);
1230        }
1231
1232        @Override
1233        public void removeSystemAudioModeChangeListener(
1234                final IHdmiSystemAudioModeChangeListener listener) {
1235            enforceAccessPermission();
1236            HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
1237        }
1238
1239        @Override
1240        public void setInputChangeListener(final IHdmiInputChangeListener listener) {
1241            enforceAccessPermission();
1242            HdmiControlService.this.setInputChangeListener(listener);
1243        }
1244
1245        @Override
1246        public List<HdmiDeviceInfo> getInputDevices() {
1247            enforceAccessPermission();
1248            // No need to hold the lock for obtaining TV device as the local device instance
1249            // is preserved while the HDMI control is enabled.
1250            HdmiCecLocalDeviceTv tv = tv();
1251            synchronized (mLock) {
1252                List<HdmiDeviceInfo> cecDevices = (tv == null)
1253                        ? Collections.<HdmiDeviceInfo>emptyList()
1254                        : tv.getSafeExternalInputsLocked();
1255                return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
1256            }
1257        }
1258
1259        @Override
1260        public void setSystemAudioVolume(final int oldIndex, final int newIndex,
1261                final int maxIndex) {
1262            enforceAccessPermission();
1263            runOnServiceThread(new Runnable() {
1264                @Override
1265                public void run() {
1266                    HdmiCecLocalDeviceTv tv = tv();
1267                    if (tv == null) {
1268                        Slog.w(TAG, "Local tv device not available");
1269                        return;
1270                    }
1271                    tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
1272                }
1273            });
1274        }
1275
1276        @Override
1277        public void setSystemAudioMute(final boolean mute) {
1278            enforceAccessPermission();
1279            runOnServiceThread(new Runnable() {
1280                @Override
1281                public void run() {
1282                    HdmiCecLocalDeviceTv tv = tv();
1283                    if (tv == null) {
1284                        Slog.w(TAG, "Local tv device not available");
1285                        return;
1286                    }
1287                    tv.changeMute(mute);
1288                }
1289            });
1290        }
1291
1292        @Override
1293        public void setArcMode(final boolean enabled) {
1294            enforceAccessPermission();
1295            runOnServiceThread(new Runnable() {
1296                @Override
1297                public void run() {
1298                    HdmiCecLocalDeviceTv tv = tv();
1299                    if (tv == null) {
1300                        Slog.w(TAG, "Local tv device not available to change arc mode.");
1301                        return;
1302                    }
1303                }
1304            });
1305        }
1306
1307        @Override
1308        public void setProhibitMode(final boolean enabled) {
1309            enforceAccessPermission();
1310            if (!isTvDevice()) {
1311                return;
1312            }
1313            HdmiControlService.this.setProhibitMode(enabled);
1314        }
1315
1316        @Override
1317        public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
1318                final int deviceType) {
1319            enforceAccessPermission();
1320            HdmiControlService.this.addVendorCommandListener(listener, deviceType);
1321        }
1322
1323        @Override
1324        public void sendVendorCommand(final int deviceType, final int targetAddress,
1325                final byte[] params, final boolean hasVendorId) {
1326            enforceAccessPermission();
1327            runOnServiceThread(new Runnable() {
1328                @Override
1329                public void run() {
1330                    HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1331                    if (device == null) {
1332                        Slog.w(TAG, "Local device not available");
1333                        return;
1334                    }
1335                    if (hasVendorId) {
1336                        sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1337                                device.getDeviceInfo().getLogicalAddress(), targetAddress,
1338                                getVendorId(), params));
1339                    } else {
1340                        sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1341                                device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1342                    }
1343                }
1344            });
1345        }
1346
1347        @Override
1348        public void sendStandby(final int deviceType, final int deviceId) {
1349            enforceAccessPermission();
1350            runOnServiceThread(new Runnable() {
1351                @Override
1352                public void run() {
1353                    HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1354                    if (device == null) {
1355                        Slog.w(TAG, "Local device not available");
1356                        return;
1357                    }
1358                    device.sendStandby(deviceId);
1359                }
1360            });
1361        }
1362
1363        @Override
1364        public void setHdmiRecordListener(IHdmiRecordListener listener) {
1365            HdmiControlService.this.setHdmiRecordListener(listener);
1366        }
1367
1368        @Override
1369        public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
1370            runOnServiceThread(new Runnable() {
1371                @Override
1372                public void run() {
1373                    if (!isTvDevice()) {
1374                        Slog.w(TAG, "No TV is available.");
1375                        return;
1376                    }
1377                    tv().startOneTouchRecord(recorderAddress, recordSource);
1378                }
1379            });
1380        }
1381
1382        @Override
1383        public void stopOneTouchRecord(final int recorderAddress) {
1384            runOnServiceThread(new Runnable() {
1385                @Override
1386                public void run() {
1387                    if (!isTvDevice()) {
1388                        Slog.w(TAG, "No TV is available.");
1389                        return;
1390                    }
1391                    tv().stopOneTouchRecord(recorderAddress);
1392                }
1393            });
1394        }
1395
1396        @Override
1397        public void startTimerRecording(final int recorderAddress, final int sourceType,
1398                final byte[] recordSource) {
1399            runOnServiceThread(new Runnable() {
1400                @Override
1401                public void run() {
1402                    if (!isTvDevice()) {
1403                        Slog.w(TAG, "No TV is available.");
1404                        return;
1405                    }
1406                    tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1407                }
1408            });
1409        }
1410
1411        @Override
1412        public void clearTimerRecording(final int recorderAddress, final int sourceType,
1413                final byte[] recordSource) {
1414            runOnServiceThread(new Runnable() {
1415                @Override
1416                public void run() {
1417                    if (!isTvDevice()) {
1418                        Slog.w(TAG, "No TV is available.");
1419                        return;
1420                    }
1421                    tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1422                }
1423            });
1424        }
1425
1426        @Override
1427        public void sendMhlVendorCommand(final int portId, final int offset, final int length,
1428                final byte[] data) {
1429            enforceAccessPermission();
1430            runOnServiceThread(new Runnable() {
1431                @Override
1432                public void run() {
1433                    if (!isControlEnabled()) {
1434                        Slog.w(TAG, "Hdmi control is disabled.");
1435                        return ;
1436                    }
1437                    HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId);
1438                    if (device == null) {
1439                        Slog.w(TAG, "Invalid port id:" + portId);
1440                        return;
1441                    }
1442                    mMhlController.sendVendorCommand(portId, offset, length, data);
1443                }
1444            });
1445        }
1446
1447        @Override
1448        public void addHdmiMhlVendorCommandListener(
1449                IHdmiMhlVendorCommandListener listener) {
1450            enforceAccessPermission();
1451            HdmiControlService.this.addHdmiMhlVendorCommandListener(listener);
1452        }
1453
1454        @Override
1455        protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1456            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
1457            final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
1458
1459            pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
1460            pw.println("mProhibitMode: " + mProhibitMode);
1461            if (mCecController != null) {
1462                pw.println("mCecController: ");
1463                pw.increaseIndent();
1464                mCecController.dump(pw);
1465                pw.decreaseIndent();
1466            }
1467            pw.println("mPortInfo: ");
1468            pw.increaseIndent();
1469            for (HdmiPortInfo hdmiPortInfo : mPortInfo) {
1470                pw.println("- " + hdmiPortInfo);
1471            }
1472            pw.decreaseIndent();
1473            pw.println("mPowerStatus: " + mPowerStatus);
1474        }
1475    }
1476
1477    @ServiceThreadOnly
1478    private void oneTouchPlay(final IHdmiControlCallback callback) {
1479        assertRunOnServiceThread();
1480        HdmiCecLocalDevicePlayback source = playback();
1481        if (source == null) {
1482            Slog.w(TAG, "Local playback device not available");
1483            invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1484            return;
1485        }
1486        source.oneTouchPlay(callback);
1487    }
1488
1489    @ServiceThreadOnly
1490    private void queryDisplayStatus(final IHdmiControlCallback callback) {
1491        assertRunOnServiceThread();
1492        HdmiCecLocalDevicePlayback source = playback();
1493        if (source == null) {
1494            Slog.w(TAG, "Local playback device not available");
1495            invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1496            return;
1497        }
1498        source.queryDisplayStatus(callback);
1499    }
1500
1501    private void addHotplugEventListener(IHdmiHotplugEventListener listener) {
1502        HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
1503        try {
1504            listener.asBinder().linkToDeath(record, 0);
1505        } catch (RemoteException e) {
1506            Slog.w(TAG, "Listener already died");
1507            return;
1508        }
1509        synchronized (mLock) {
1510            mHotplugEventListenerRecords.add(record);
1511        }
1512    }
1513
1514    private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
1515        synchronized (mLock) {
1516            for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1517                if (record.mListener.asBinder() == listener.asBinder()) {
1518                    listener.asBinder().unlinkToDeath(record, 0);
1519                    mHotplugEventListenerRecords.remove(record);
1520                    break;
1521                }
1522            }
1523        }
1524    }
1525
1526    private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
1527        DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
1528        try {
1529            listener.asBinder().linkToDeath(record, 0);
1530        } catch (RemoteException e) {
1531            Slog.w(TAG, "Listener already died");
1532            return;
1533        }
1534        synchronized (mLock) {
1535            mDeviceEventListenerRecords.add(record);
1536        }
1537    }
1538
1539    void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
1540        synchronized (mLock) {
1541            for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
1542                try {
1543                    record.mListener.onStatusChanged(device, status);
1544                } catch (RemoteException e) {
1545                    Slog.e(TAG, "Failed to report device event:" + e);
1546                }
1547            }
1548        }
1549    }
1550
1551    private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
1552        SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
1553                listener);
1554        try {
1555            listener.asBinder().linkToDeath(record, 0);
1556        } catch (RemoteException e) {
1557            Slog.w(TAG, "Listener already died");
1558            return;
1559        }
1560        synchronized (mLock) {
1561            mSystemAudioModeChangeListenerRecords.add(record);
1562        }
1563    }
1564
1565    private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
1566        synchronized (mLock) {
1567            for (SystemAudioModeChangeListenerRecord record :
1568                    mSystemAudioModeChangeListenerRecords) {
1569                if (record.mListener.asBinder() == listener) {
1570                    listener.asBinder().unlinkToDeath(record, 0);
1571                    mSystemAudioModeChangeListenerRecords.remove(record);
1572                    break;
1573                }
1574            }
1575        }
1576    }
1577
1578    private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
1579        private final IHdmiInputChangeListener mListener;
1580
1581        public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
1582            mListener = listener;
1583        }
1584
1585        @Override
1586        public void binderDied() {
1587            synchronized (mLock) {
1588                mInputChangeListenerRecord = null;
1589            }
1590        }
1591    }
1592
1593    private void setInputChangeListener(IHdmiInputChangeListener listener) {
1594        synchronized (mLock) {
1595            mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
1596            try {
1597                listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
1598            } catch (RemoteException e) {
1599                Slog.w(TAG, "Listener already died");
1600                return;
1601            }
1602        }
1603    }
1604
1605    void invokeInputChangeListener(HdmiDeviceInfo info) {
1606        synchronized (mLock) {
1607            if (mInputChangeListenerRecord != null) {
1608                try {
1609                    mInputChangeListenerRecord.mListener.onChanged(info);
1610                } catch (RemoteException e) {
1611                    Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
1612                }
1613            }
1614        }
1615    }
1616
1617    private void setHdmiRecordListener(IHdmiRecordListener listener) {
1618        synchronized (mLock) {
1619            mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
1620            try {
1621                listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
1622            } catch (RemoteException e) {
1623                Slog.w(TAG, "Listener already died.", e);
1624            }
1625        }
1626    }
1627
1628    byte[] invokeRecordRequestListener(int recorderAddress) {
1629        synchronized (mLock) {
1630            if (mRecordListenerRecord != null) {
1631                try {
1632                    return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
1633                } catch (RemoteException e) {
1634                    Slog.w(TAG, "Failed to start record.", e);
1635                }
1636            }
1637            return EmptyArray.BYTE;
1638        }
1639    }
1640
1641    void invokeOneTouchRecordResult(int result) {
1642        synchronized (mLock) {
1643            if (mRecordListenerRecord != null) {
1644                try {
1645                    mRecordListenerRecord.mListener.onOneTouchRecordResult(result);
1646                } catch (RemoteException e) {
1647                    Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
1648                }
1649            }
1650        }
1651    }
1652
1653    void invokeTimerRecordingResult(int result) {
1654        synchronized (mLock) {
1655            if (mRecordListenerRecord != null) {
1656                try {
1657                    mRecordListenerRecord.mListener.onTimerRecordingResult(result);
1658                } catch (RemoteException e) {
1659                    Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
1660                }
1661            }
1662        }
1663    }
1664
1665    void invokeClearTimerRecordingResult(int result) {
1666        synchronized (mLock) {
1667            if (mRecordListenerRecord != null) {
1668                try {
1669                    mRecordListenerRecord.mListener.onClearTimerRecordingResult(result);
1670                } catch (RemoteException e) {
1671                    Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
1672                }
1673            }
1674        }
1675    }
1676
1677    private void invokeCallback(IHdmiControlCallback callback, int result) {
1678        try {
1679            callback.onComplete(result);
1680        } catch (RemoteException e) {
1681            Slog.e(TAG, "Invoking callback failed:" + e);
1682        }
1683    }
1684
1685    private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
1686            boolean enabled) {
1687        try {
1688            listener.onStatusChanged(enabled);
1689        } catch (RemoteException e) {
1690            Slog.e(TAG, "Invoking callback failed:" + e);
1691        }
1692    }
1693
1694    private void announceHotplugEvent(int portId, boolean connected) {
1695        HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
1696        synchronized (mLock) {
1697            for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1698                invokeHotplugEventListenerLocked(record.mListener, event);
1699            }
1700        }
1701    }
1702
1703    private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
1704            HdmiHotplugEvent event) {
1705        try {
1706            listener.onReceived(event);
1707        } catch (RemoteException e) {
1708            Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1709        }
1710    }
1711
1712    private HdmiCecLocalDeviceTv tv() {
1713        return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
1714    }
1715
1716    boolean isTvDevice() {
1717        return tv() != null;
1718    }
1719
1720    private HdmiCecLocalDevicePlayback playback() {
1721        return (HdmiCecLocalDevicePlayback)
1722                mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
1723    }
1724
1725    AudioManager getAudioManager() {
1726        return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
1727    }
1728
1729    boolean isControlEnabled() {
1730        synchronized (mLock) {
1731            return mHdmiControlEnabled;
1732        }
1733    }
1734
1735    @ServiceThreadOnly
1736    int getPowerStatus() {
1737        assertRunOnServiceThread();
1738        return mPowerStatus;
1739    }
1740
1741    @ServiceThreadOnly
1742    boolean isPowerOnOrTransient() {
1743        assertRunOnServiceThread();
1744        return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
1745                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1746    }
1747
1748    @ServiceThreadOnly
1749    boolean isPowerStandbyOrTransient() {
1750        assertRunOnServiceThread();
1751        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
1752                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1753    }
1754
1755    @ServiceThreadOnly
1756    boolean isPowerStandby() {
1757        assertRunOnServiceThread();
1758        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
1759    }
1760
1761    @ServiceThreadOnly
1762    void wakeUp() {
1763        assertRunOnServiceThread();
1764        mWakeUpMessageReceived = true;
1765        PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1766        pm.wakeUp(SystemClock.uptimeMillis());
1767        // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
1768        // the intent, the sequence will continue at onWakeUp().
1769    }
1770
1771    @ServiceThreadOnly
1772    void standby() {
1773        assertRunOnServiceThread();
1774        mStandbyMessageReceived = true;
1775        PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1776        pm.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
1777        // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
1778        // the intent, the sequence will continue at onStandby().
1779    }
1780
1781    @ServiceThreadOnly
1782    private void onWakeUp() {
1783        assertRunOnServiceThread();
1784        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1785        if (mCecController != null) {
1786            if (mHdmiControlEnabled) {
1787                int startReason = INITIATED_BY_SCREEN_ON;
1788                if (mWakeUpMessageReceived) {
1789                    startReason = INITIATED_BY_WAKE_UP_MESSAGE;
1790                }
1791                initializeCec(startReason);
1792            }
1793        } else {
1794            Slog.i(TAG, "Device does not support HDMI-CEC.");
1795        }
1796        // TODO: Initialize MHL local devices.
1797    }
1798
1799    @ServiceThreadOnly
1800    private void onStandby() {
1801        assertRunOnServiceThread();
1802        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1803
1804        final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
1805        disableDevices(new PendingActionClearedCallback() {
1806            @Override
1807            public void onCleared(HdmiCecLocalDevice device) {
1808                Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
1809                devices.remove(device);
1810                if (devices.isEmpty()) {
1811                    onStandbyCompleted();
1812                    // We will not clear local devices here, since some OEM/SOC will keep passing
1813                    // the received packets until the application processor enters to the sleep
1814                    // actually.
1815                }
1816            }
1817        });
1818    }
1819
1820    @ServiceThreadOnly
1821    private void onLanguageChanged(String language) {
1822        assertRunOnServiceThread();
1823        mLanguage = language;
1824
1825        if (isTvDevice()) {
1826            tv().broadcastMenuLanguage(language);
1827        }
1828    }
1829
1830    @ServiceThreadOnly
1831    String getLanguage() {
1832        assertRunOnServiceThread();
1833        return mLanguage;
1834    }
1835
1836    private void disableDevices(PendingActionClearedCallback callback) {
1837        if (mCecController != null) {
1838            for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1839                device.disableDevice(mStandbyMessageReceived, callback);
1840            }
1841            if (isTvDevice()) {
1842                unregisterSettingsObserver();
1843            }
1844        }
1845
1846        mMhlController.clearAllLocalDevices();
1847    }
1848
1849    @ServiceThreadOnly
1850    private void clearLocalDevices() {
1851        assertRunOnServiceThread();
1852        if (mCecController == null) {
1853            return;
1854        }
1855        mCecController.clearLogicalAddress();
1856        mCecController.clearLocalDevices();
1857    }
1858
1859    @ServiceThreadOnly
1860    private void onStandbyCompleted() {
1861        assertRunOnServiceThread();
1862        Slog.v(TAG, "onStandbyCompleted");
1863
1864        if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
1865            return;
1866        }
1867        mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
1868        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1869            device.onStandby(mStandbyMessageReceived);
1870        }
1871        mStandbyMessageReceived = false;
1872        mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, DISABLED);
1873    }
1874
1875    private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
1876        VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
1877        try {
1878            listener.asBinder().linkToDeath(record, 0);
1879        } catch (RemoteException e) {
1880            Slog.w(TAG, "Listener already died");
1881            return;
1882        }
1883        synchronized (mLock) {
1884            mVendorCommandListenerRecords.add(record);
1885        }
1886    }
1887
1888    boolean invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params,
1889            boolean hasVendorId) {
1890        synchronized (mLock) {
1891            if (mVendorCommandListenerRecords.isEmpty()) {
1892                return false;
1893            }
1894            for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
1895                if (record.mDeviceType != deviceType) {
1896                    continue;
1897                }
1898                try {
1899                    record.mListener.onReceived(srcAddress, params, hasVendorId);
1900                } catch (RemoteException e) {
1901                    Slog.e(TAG, "Failed to notify vendor command reception", e);
1902                }
1903            }
1904            return true;
1905        }
1906    }
1907
1908    private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
1909        HdmiMhlVendorCommandListenerRecord record =
1910                new HdmiMhlVendorCommandListenerRecord(listener);
1911        try {
1912            listener.asBinder().linkToDeath(record, 0);
1913        } catch (RemoteException e) {
1914            Slog.w(TAG, "Listener already died.");
1915            return;
1916        }
1917
1918        synchronized (mLock) {
1919            mMhlVendorCommandListenerRecords.add(record);
1920        }
1921    }
1922
1923    void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
1924        synchronized (mLock) {
1925            for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
1926                try {
1927                    record.mListener.onReceived(portId, offest, length, data);
1928                } catch (RemoteException e) {
1929                    Slog.e(TAG, "Failed to notify MHL vendor command", e);
1930                }
1931            }
1932        }
1933    }
1934
1935    boolean isProhibitMode() {
1936        synchronized (mLock) {
1937            return mProhibitMode;
1938        }
1939    }
1940
1941    void setProhibitMode(boolean enabled) {
1942        synchronized (mLock) {
1943            mProhibitMode = enabled;
1944        }
1945    }
1946
1947    @ServiceThreadOnly
1948    void setCecOption(int key, int value) {
1949        assertRunOnServiceThread();
1950        mCecController.setOption(key, value);
1951    }
1952
1953    @ServiceThreadOnly
1954    void setControlEnabled(boolean enabled) {
1955        assertRunOnServiceThread();
1956
1957        int value = toInt(enabled);
1958        mCecController.setOption(OPTION_CEC_ENABLE, value);
1959        mMhlController.setOption(OPTION_MHL_ENABLE, value);
1960
1961        synchronized (mLock) {
1962            mHdmiControlEnabled = enabled;
1963        }
1964
1965        if (enabled) {
1966            initializeCec(INITIATED_BY_ENABLE_CEC);
1967        } else {
1968            disableDevices(new PendingActionClearedCallback() {
1969                @Override
1970                public void onCleared(HdmiCecLocalDevice device) {
1971                    assertRunOnServiceThread();
1972                    clearLocalDevices();
1973                }
1974            });
1975        }
1976    }
1977
1978    @ServiceThreadOnly
1979    void setActivePortId(int portId) {
1980        assertRunOnServiceThread();
1981        mActivePortId = portId;
1982
1983        // Resets last input for MHL, which stays valid only after the MHL device was selected,
1984        // and no further switching is done.
1985        setLastInputForMhl(Constants.INVALID_PORT_ID);
1986    }
1987
1988    @ServiceThreadOnly
1989    void setLastInputForMhl(int portId) {
1990        assertRunOnServiceThread();
1991        mLastInputMhl = portId;
1992    }
1993
1994    @ServiceThreadOnly
1995    int getLastInputForMhl() {
1996        assertRunOnServiceThread();
1997        return mLastInputMhl;
1998    }
1999
2000    /**
2001     * Performs input change, routing control for MHL device.
2002     *
2003     * @param portId MHL port, or the last port to go back to if {@code contentOn} is false
2004     * @param contentOn {@code true} if RAP data is content on; otherwise false
2005     */
2006    @ServiceThreadOnly
2007    void changeInputForMhl(int portId, boolean contentOn) {
2008        assertRunOnServiceThread();
2009        final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
2010        tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
2011            @Override
2012            public void onComplete(int result) throws RemoteException {
2013                // Keep the last input to switch back later when RAP[ContentOff] is received.
2014                // This effectively sets the port to invalid one if the switching is for
2015                // RAP[ContentOff].
2016                setLastInputForMhl(lastInput);
2017            }
2018        });
2019
2020        // MHL device is always directly connected to the port. Update the active port ID to avoid
2021        // unnecessary post-routing control task.
2022        tv().setActivePortId(portId);
2023
2024        // The port is either the MHL-enabled port where the mobile device is connected, or
2025        // the last port to go back to when turnoff command is received. Note that the last port
2026        // may not be the MHL-enabled one. In this case the device info to be passed to
2027        // input change listener should be the one describing the corresponding HDMI port.
2028        HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId);
2029        HdmiDeviceInfo info = (device != null && device.getInfo() != null)
2030                ? device.getInfo()
2031                : mPortDeviceMap.get(portId);
2032        invokeInputChangeListener(info);
2033    }
2034
2035   void setMhlInputChangeEnabled(boolean enabled) {
2036       mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
2037
2038        synchronized (mLock) {
2039            mMhlInputChangeEnabled = enabled;
2040        }
2041    }
2042
2043    boolean isMhlInputChangeEnabled() {
2044        synchronized (mLock) {
2045            return mMhlInputChangeEnabled;
2046        }
2047    }
2048
2049    @ServiceThreadOnly
2050    void displayOsd(int messageId) {
2051        assertRunOnServiceThread();
2052        Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2053        intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2054        getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2055                HdmiControlService.PERMISSION);
2056    }
2057
2058    @ServiceThreadOnly
2059    void displayOsd(int messageId, int extra) {
2060        assertRunOnServiceThread();
2061        Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2062        intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2063        intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRAM_PARAM1, extra);
2064        getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2065                HdmiControlService.PERMISSION);
2066    }
2067}
2068