From cdf27513762c3313f8ef8e606bb31533e5f25087 Mon Sep 17 00:00:00 2001 From: Pawit Pornkitprasan Date: Tue, 6 Dec 2011 14:45:41 +0700 Subject: Add TV Out support --- AriesParts/Android.mk | 3 + AriesParts/AndroidManifest.xml | 5 + AriesParts/res/values/arrays.xml | 10 ++ AriesParts/res/values/strings.xml | 8 ++ AriesParts/res/xml/main.xml | 16 +++ AriesParts/src/android/hardware/TvOut.java | 70 ++++++++++ .../src/com/cyanogenmod/AriesParts/AriesParts.java | 84 ++++++++++++ .../com/cyanogenmod/AriesParts/TvOutService.java | 146 +++++++++++++++++++++ device_base.mk | 3 +- init.aries.rc | 10 ++ tvouthack/Android.mk | 27 ++++ tvouthack/README.txt | 5 + tvouthack/main.cpp | 49 +++++++ ueventd.aries.rc | 5 + 14 files changed, 440 insertions(+), 1 deletion(-) create mode 100644 AriesParts/src/android/hardware/TvOut.java create mode 100644 AriesParts/src/com/cyanogenmod/AriesParts/TvOutService.java create mode 100644 tvouthack/Android.mk create mode 100644 tvouthack/README.txt create mode 100644 tvouthack/main.cpp diff --git a/AriesParts/Android.mk b/AriesParts/Android.mk index 29efe75..5db7396 100644 --- a/AriesParts/Android.mk +++ b/AriesParts/Android.mk @@ -8,6 +8,9 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_PACKAGE_NAME := AriesParts LOCAL_CERTIFICATE := platform +# Required so that symbols used by the jni library doesn't get stripped out +LOCAL_PROGUARD_ENABLED := disabled + include $(BUILD_PACKAGE) include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/AriesParts/AndroidManifest.xml b/AriesParts/AndroidManifest.xml index d108fb8..a2213b4 100644 --- a/AriesParts/AndroidManifest.xml +++ b/AriesParts/AndroidManifest.xml @@ -9,11 +9,16 @@ + + + + + diff --git a/AriesParts/res/values/arrays.xml b/AriesParts/res/values/arrays.xml index a5db723..5d32de2 100644 --- a/AriesParts/res/values/arrays.xml +++ b/AriesParts/res/values/arrays.xml @@ -32,4 +32,14 @@ 22 23 + + + NTSC + PAL + + + + 1 + 2 + diff --git a/AriesParts/res/values/strings.xml b/AriesParts/res/values/strings.xml index c69049d..735ffe7 100644 --- a/AriesParts/res/values/strings.xml +++ b/AriesParts/res/values/strings.xml @@ -16,4 +16,12 @@ Radio HSPA Enable HSDPA/HSUPA + + TV Out + TV Out + TV Out cable not connected + TV Out cable connected, check to enable + TV Out enabled + TV System + Set your TV system for TV Out diff --git a/AriesParts/res/xml/main.xml b/AriesParts/res/xml/main.xml index e74b599..9c5ff22 100644 --- a/AriesParts/res/xml/main.xml +++ b/AriesParts/res/xml/main.xml @@ -33,4 +33,20 @@ android:entryValues="@array/hspa_entries_values" android:defaultValue="23" /> + + + + + diff --git a/AriesParts/src/android/hardware/TvOut.java b/AriesParts/src/android/hardware/TvOut.java new file mode 100644 index 0000000..6c0936e --- /dev/null +++ b/AriesParts/src/android/hardware/TvOut.java @@ -0,0 +1,70 @@ +package android.hardware; + +import java.lang.ref.WeakReference; + +import android.graphics.Bitmap; +import android.util.Log; + +public class TvOut { + private static final String TAG = "TvOut"; + + private int mListenerContext; + private int mNativeContext; + + public native void _DisableTvOut(); + + public native void _EnableTvOut(); + + public native void _SetOrientation(int paramInt); + + public native void _SetTvScreenSize(int paramInt); + + public native void _SetTvSystem(int paramInt); + + public native void _TvOutResume(int paramInt); + + public native void _TvOutSetImageString(String paramString); + + public native void _TvOutSuspend(String paramString); + + public native boolean _TvoutSubtitleIsEnable(); + + public native boolean _TvoutSubtitlePostBitmap(Bitmap paramBitmap, int paramInt); + + public native boolean _TvoutSubtitleSetStatus(int paramInt); + + public native int _getSubtitleHDMIHeight(); + + public native int _getSubtitleHDMIWidth(); + + public native boolean _isEnabled(); + + public native boolean _isSuspended(); + + public native boolean _isTvoutCableConnected(); + + private final native void _native_setup(Object paramObject); + + private final native void _release(); + + public native void _setTvoutCableConnected(int paramInt); + + static { + System.loadLibrary("tvout_jni"); + } + + public TvOut() { + _native_setup(new WeakReference(this)); + } + + public void finalize() { + _release(); + } + + private static void postEventFromNative(Object tvOutRef, int what, int arg1, int arg2, Object obj) { + TvOut tvOut = (TvOut)((WeakReference)tvOutRef).get(); + + Log.d(TAG, "Native Event: " + what + " " + arg1 + " " + arg2); + } + +} \ No newline at end of file diff --git a/AriesParts/src/com/cyanogenmod/AriesParts/AriesParts.java b/AriesParts/src/com/cyanogenmod/AriesParts/AriesParts.java index 103e91b..3d6a68f 100644 --- a/AriesParts/src/com/cyanogenmod/AriesParts/AriesParts.java +++ b/AriesParts/src/com/cyanogenmod/AriesParts/AriesParts.java @@ -1,7 +1,14 @@ package com.cyanogenmod.AriesParts; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.TvOut; import android.os.Bundle; +import android.preference.CheckBoxPreference; import android.preference.ListPreference; +import android.preference.Preference; import android.preference.PreferenceActivity; public class AriesParts extends PreferenceActivity { @@ -9,10 +16,25 @@ public class AriesParts extends PreferenceActivity { public static final String KEY_COLOR_TUNING = "color_tuning"; public static final String KEY_BACKLIGHT_TIMEOUT = "backlight_timeout"; public static final String KEY_HSPA = "hspa"; + public static final String KEY_TVOUT_ENABLE = "tvout_enable"; + public static final String KEY_TVOUT_SYSTEM = "tvout_system"; private ColorTuningPreference mColorTuning; private ListPreference mBacklightTimeout; private ListPreference mHspa; + private CheckBoxPreference mTvOutEnable; + private ListPreference mTvOutSystem; + private TvOut mTvOut; + + private BroadcastReceiver mHeadsetReceiver = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + int state = intent.getIntExtra("state", 0); + updateTvOutEnable(state != 0); + } + + }; @Override public void onCreate(Bundle savedInstanceState) { @@ -29,6 +51,68 @@ public class AriesParts extends PreferenceActivity { mHspa = (ListPreference) findPreference(KEY_HSPA); mHspa.setEnabled(Hspa.isSupported()); mHspa.setOnPreferenceChangeListener(new Hspa(this)); + + mTvOut = new TvOut(); + mTvOutEnable = (CheckBoxPreference) findPreference(KEY_TVOUT_ENABLE); + mTvOutEnable.setChecked(mTvOut._isEnabled()); + + mTvOutEnable.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + boolean enable = (Boolean) newValue; + Intent i = new Intent(AriesParts.this, TvOutService.class); + i.putExtra(TvOutService.EXTRA_COMMAND, enable ? TvOutService.COMMAND_ENABLE : TvOutService.COMMAND_DISABLE); + startService(i); + return true; + } + + }); + + mTvOutSystem = (ListPreference) findPreference(KEY_TVOUT_SYSTEM); + mTvOutSystem.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (mTvOut._isEnabled()) { + int newSystem = Integer.valueOf((String) newValue); + Intent i = new Intent(AriesParts.this, TvOutService.class); + i.putExtra(TvOutService.EXTRA_COMMAND, TvOutService.COMMAND_CHANGE_SYSTEM); + i.putExtra(TvOutService.EXTRA_SYSTEM, newSystem); + startService(i); + } + return true; + } + + }); + } + + @Override + protected void onResume() { + super.onResume(); + registerReceiver(mHeadsetReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG)); + } + + @Override + protected void onPause() { + super.onPause(); + unregisterReceiver(mHeadsetReceiver); + } + + private void updateTvOutEnable(boolean connected) { + mTvOutEnable.setEnabled(connected); + mTvOutEnable.setSummaryOff(connected ? R.string.tvout_enable_summary : R.string.tvout_enable_summary_nocable); + + if (!connected && mTvOutEnable.isChecked()) { + // Disable on unplug (UI) + mTvOutEnable.setChecked(false); + } + } + + @Override + protected void onDestroy() { + mTvOut.finalize(); + super.onDestroy(); } } diff --git a/AriesParts/src/com/cyanogenmod/AriesParts/TvOutService.java b/AriesParts/src/com/cyanogenmod/AriesParts/TvOutService.java new file mode 100644 index 0000000..5812ac6 --- /dev/null +++ b/AriesParts/src/com/cyanogenmod/AriesParts/TvOutService.java @@ -0,0 +1,146 @@ +package com.cyanogenmod.AriesParts; + +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.hardware.TvOut; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemProperties; +import android.preference.PreferenceManager; +import android.view.Display; +import android.view.IRotationWatcher; +import android.view.IWindowManager; +import android.view.WindowManager; + +public class TvOutService extends Service { + + public static final String EXTRA_COMMAND = "command"; + public static final String EXTRA_SYSTEM = "system"; + public static final String COMMAND_ENABLE = "enable"; + public static final String COMMAND_DISABLE = "disable"; + public static final String COMMAND_CHANGE_SYSTEM = "system"; + + private TvOut mTvOut; + private SharedPreferences mPref; + private int mSystem; + private boolean mWasOn = false; // For enabling on screen on + + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_HEADSET_PLUG.equals(action)) { + int state = intent.getIntExtra("state", 0); + if (state == 0 && mTvOut._isEnabled()) { + // Disable when cable is unplugged + mWasOn = false; + disable(); + stopSelf(); + } + } + else if (Intent.ACTION_SCREEN_ON.equals(action)) { + if (mWasOn) { + enable(); + mWasOn = false; + } + } + else if (Intent.ACTION_SCREEN_OFF.equals(action)) { + if (mTvOut._isEnabled()) { + mWasOn = true; + disable(); + } + } + } + + }; + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + mTvOut = new TvOut(); + mPref = PreferenceManager.getDefaultSharedPreferences(this); + + IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window")); + try { + wm.watchRotation(new IRotationWatcher.Stub() { + @Override + public void onRotationChanged(int rotation) { + TvOutService.this.onRotationChanged(rotation); + } + }); + } + catch (RemoteException e) { } + + IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); + filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(Intent.ACTION_SCREEN_ON); + registerReceiver(mReceiver, filter); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (intent != null) { + String command = intent.getStringExtra("command"); + if (COMMAND_ENABLE.equals(command)) { + mSystem = Integer.parseInt(mPref.getString(AriesParts.KEY_TVOUT_SYSTEM, "2")); // Default = PAL + enable(); + } + else if (COMMAND_DISABLE.equals(command)) { + disable(); + stopSelf(); + } + else if (COMMAND_CHANGE_SYSTEM.equals(command)) { + if (mTvOut._isEnabled()) { + mSystem = intent.getIntExtra(EXTRA_SYSTEM, 2); + disable(); + enable(); + } + } + } + + return START_STICKY; + } + + @Override + public void onDestroy() { + unregisterReceiver(mReceiver); + mTvOut.finalize(); + super.onDestroy(); + } + + public void onRotationChanged(int rotation) { + mTvOut._SetOrientation(rotation); + } + + private void enable() { + mTvOut._SetTvSystem(mSystem); + mTvOut._TvOutSetImageString("TV-out not supported while application running. Phone display only"); + + Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + mTvOut._SetOrientation(display.getRotation()); + + mTvOut._EnableTvOut(); + mTvOut._setTvoutCableConnected(1); + + // Start tvouthack service used to bombard screen refresh messages + SystemProperties.set("ctl.start", "tvouthack"); + } + + private void disable() { + SystemProperties.set("ctl.stop", "tvouthack"); + + mTvOut._DisableTvOut(); + mTvOut._setTvoutCableConnected(0); + } + +} diff --git a/device_base.mk b/device_base.mk index 0d576ea..0819994 100644 --- a/device_base.mk +++ b/device_base.mk @@ -110,7 +110,8 @@ PRODUCT_PACKAGES += \ # Device-specific packages PRODUCT_PACKAGES += \ SamsungServiceMode \ - AriesParts + AriesParts \ + tvouthack # apns config file PRODUCT_COPY_FILES += \ diff --git a/init.aries.rc b/init.aries.rc index 5d99a99..5603d23 100644 --- a/init.aries.rc +++ b/init.aries.rc @@ -196,3 +196,13 @@ service geomagneticd /system/vendor/bin/geomagneticd class late_start user compass group system input + +service tvout /system/bin/tvoutserver + class late_start + user system + group graphics + +service tvouthack /system/bin/tvouthack + user system + group graphics + disabled diff --git a/tvouthack/Android.mk b/tvouthack/Android.mk new file mode 100644 index 0000000..a38be88 --- /dev/null +++ b/tvouthack/Android.mk @@ -0,0 +1,27 @@ +# Copyright (C) 2011 Pawit Pornkitprasan +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := main.cpp + +LOCAL_SHARED_LIBRARIES := libutils libbinder + +LOCAL_MODULE := tvouthack + +include $(BUILD_EXECUTABLE) + diff --git a/tvouthack/README.txt b/tvouthack/README.txt new file mode 100644 index 0000000..bea0307 --- /dev/null +++ b/tvouthack/README.txt @@ -0,0 +1,5 @@ +The proprietary daemon "tvoutserver" expects the system to tell it +when the frames should be refreshed. Samsung implemented this by +integrating TV Out in surfaceflinger and other graphic services. +However, we will just run a daemon that brute sends the signal every +150 ms. \ No newline at end of file diff --git a/tvouthack/main.cpp b/tvouthack/main.cpp new file mode 100644 index 0000000..85005c7 --- /dev/null +++ b/tvouthack/main.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include + +using namespace android; + +int main() { + sp sm = defaultServiceManager(); + sp binder; + + do { + binder = sm->getService(String16("tvout")); + if (binder != 0) break; + usleep(500000); // 0.5 s + } while(true); + + int ret; + + Parcel s2, r2; + s2.writeInterfaceToken(String16("android.hardware.ITvOutService")); + binder->transact(1, s2, &r2); + sp binder2 = r2.readStrongBinder(); + + while (true) { + { + Parcel send, reply; + int code = 4; + send.writeInterfaceToken(String16("android.hardware.ITvOut")); + int ret = binder2->transact(code, send, &reply); + } + { + Parcel send, reply; + int code = 27; + send.writeInterfaceToken(String16("android.hardware.ITvOut")); + int ret = binder2->transact(code, send, &reply); + } + { + Parcel send, reply; + int code = 13; + send.writeInterfaceToken(String16("android.hardware.ITvOut")); + send.writeInt32(0); + int ret = binder2->transact(code, send, &reply); + } + usleep(15000); // Should give ~60 fps + } + return 0; +} \ No newline at end of file diff --git a/ueventd.aries.rc b/ueventd.aries.rc index fc3276a..295a879 100644 --- a/ueventd.aries.rc +++ b/ueventd.aries.rc @@ -14,6 +14,11 @@ /dev/block/mtdblock5 0660 radio radio /dev/mtd/mtd5ro 0660 radio radio +# TV Out +/dev/video14 0660 system graphics +/dev/video21 0660 system graphics +/dev/video22 0660 system graphics + # for Sensor HAL /dev/yamaha_compass 0660 system system /dev/akm8973 0660 system system -- cgit v1.1