/* * Copyright (C) 2013 75py * * 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. */ package com.nagopy.android.xposed.utilities; import java.lang.ref.WeakReference; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Locale; import android.content.Context; import android.content.IntentFilter; import android.content.res.Resources; import android.content.res.XModuleResources; import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; import com.nagopy.android.common.pref.FontListPreference; import com.nagopy.android.common.util.DimenUtil; import com.nagopy.android.xposed.util.XConst; import com.nagopy.android.xposed.util.XUtil; import com.nagopy.android.xposed.utilities.XposedModules.HandleInitPackageResources; import com.nagopy.android.xposed.utilities.XposedModules.HandleLoadPackage; import com.nagopy.android.xposed.utilities.XposedModules.XposedModule; import com.nagopy.android.xposed.utilities.setting.ModStatusBarClockSettingsGen; import com.nagopy.android.xposed.utilities.util.Const; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XC_MethodReplacement; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_InitPackageResources.InitPackageResourcesParam; import de.robv.android.xposed.callbacks.XC_LayoutInflated; import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam; import de.robv.android.xposed.callbacks.XCallback; /** * ステータスバーの時計をカスタマイズするモジュール. */ @XposedModule(setting = ModStatusBarClockSettingsGen.class) public class ModStatusBarClock { @HandleLoadPackage(targetPackage = XConst.PKG_SYSTEM_UI, summary = "ステータスバー時計") public static void handleLoadPackage(final String modulePath, LoadPackageParam lpparam, ModStatusBarClockSettingsGen mSettings) throws Throwable { // Clockのクラスを取得 final Class<?> clockClass = XposedHelpers.findClass( "com.android.systemui.statusbar.policy.Clock", lpparam.classLoader); // 時計の文字を返すメソッドを書き換え XposedHelpers.findAndHookMethod(clockClass, "getSmallTime", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { Object additionalInstanceField = XposedHelpers .getAdditionalInstanceField(param.thisObject, Const.ADDITIONAL_DATE_FORMAT); if (additionalInstanceField == null) { // モジュールで追加した値がない場合は元のメソッドを実行 return XUtil.invokeOriginalMethod(param); } // モジュールで設定したフォーマットを使用して時計の文字を作成する Calendar mCalendar = (Calendar) XposedHelpers .getObjectField(param.thisObject, "mCalendar"); SimpleDateFormat mClockFormat = (SimpleDateFormat) additionalInstanceField; return mClockFormat.format(mCalendar.getTime()); } }); // ticker Class<?> clsTicker = XposedHelpers .findClass("com.android.systemui.statusbar.phone.PhoneStatusBar$MyTicker", lpparam.classLoader); XposedBridge.hookAllConstructors(clsTicker, new XC_MethodHook(XCallback.PRIORITY_LOWEST) { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { View mStatusBarView = (View) param.args[2]; int clock = mStatusBarView.getResources().getIdentifier("clock", "id", XConst.PKG_SYSTEM_UI); View clockView = mStatusBarView.getRootView().findViewById(clock); XposedHelpers.setAdditionalInstanceField(param.thisObject, "clockView", clockView); } }); XposedHelpers.findAndHookMethod(clsTicker, "tickerStarting", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { View clockView = (View) XposedHelpers.getAdditionalInstanceField( param.thisObject, "clockView"); // デフォルトの位置にある場合は何もしない Object currentPosition = clockView.getTag(R.id.tag_clock_current_position); if (currentPosition == null || currentPosition.equals(Const.SB_CLOCK_POSITION_DEFAULT)) { return; } clockView.setVisibility(View.GONE); ViewHolder viewHolder = (ViewHolder) clockView .getTag(R.id.tag_status_bar_clock_view_holder); Animation anim = viewHolder.mStatusBarContents.getAnimation(); clockView.startAnimation(anim); } }); XposedHelpers.findAndHookMethod(clsTicker, "tickerDone", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { View clockView = (View) XposedHelpers.getAdditionalInstanceField( param.thisObject, "clockView"); // デフォルトの位置にある場合は何もしない Object currentPosition = clockView.getTag(R.id.tag_clock_current_position); if (currentPosition == null || currentPosition.equals(Const.SB_CLOCK_POSITION_DEFAULT)) { return; } clockView.setVisibility(View.VISIBLE); ViewHolder viewHolder = (ViewHolder) clockView .getTag(R.id.tag_status_bar_clock_view_holder); Animation anim = viewHolder.mStatusBarContents.getAnimation(); clockView.startAnimation(anim); } }); XposedHelpers.findAndHookMethod(clsTicker, "tickerHalting", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { View clockView = (View) XposedHelpers.getAdditionalInstanceField( param.thisObject, "clockView"); // デフォルトの位置にある場合は何もしない Object currentPosition = clockView.getTag(R.id.tag_clock_current_position); if (currentPosition == null || currentPosition.equals(Const.SB_CLOCK_POSITION_DEFAULT)) { return; } clockView.setVisibility(View.VISIBLE); ViewHolder viewHolder = (ViewHolder) clockView .getTag(R.id.tag_status_bar_clock_view_holder); Animation anim = viewHolder.mStatusBarContents.getAnimation(); if (anim != null) { clockView.startAnimation(anim); } } }); // キーガード表示中に時計が消えるように Class<?> clsPhoneStatusBar = XposedHelpers .findClass("com.android.systemui.statusbar.phone.PhoneStatusBar", lpparam.classLoader); XposedHelpers.findAndHookMethod(clsPhoneStatusBar, "showClock", boolean.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable { View mStatusBarView = (View) XposedHelpers.getObjectField(param.thisObject, "mStatusBarView"); if (mStatusBarView == null) return null; boolean show = (Boolean) param.args[0]; int clockId = mStatusBarView.getResources().getIdentifier("clock", "id", XConst.PKG_SYSTEM_UI); View clock = mStatusBarView.findViewById(clockId); if (clock != null) { clock.setVisibility(show ? View.VISIBLE : View.GONE); } return null; } }); } @HandleInitPackageResources(targetPackage = XConst.PKG_SYSTEM_UI, summary = "ステータスバー時計(リソース)") public static void handleInitPackageResources(final String modulePath, final InitPackageResourcesParam resparam, final ModStatusBarClockSettingsGen mStatusBarClockSettings) throws Throwable { // レイアウトをごにょごにょ resparam.res.hookLayout(XConst.PKG_SYSTEM_UI, "layout", "super_status_bar", new XC_LayoutInflated(-7575) { // 優先度低 @Override public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable { // 時計のビューを取得 TextView clock = (TextView) liparam.view .findViewById(liparam.res.getIdentifier( "clock", "id", XConst.PKG_SYSTEM_UI)); // GravityBoxとの競合チェック、ViewHolderセットなど setViewHolder(clock); // デフォルト値を保存 mStatusBarClockSettings.defaultStatusBarClockTextSize = clock .getTextSize(); mStatusBarClockSettings.defaultStatusBarClockTextColor = clock .getTextColors().getDefaultColor(); mStatusBarClockSettings.defaultGravity = clock .getGravity(); mStatusBarClockSettings.defaultTypeface = clock .getTypeface(); // モジュールリソース取得用の値をDaoに追加 mStatusBarClockSettings.moduleResources = XModuleResources .createInstance(modulePath, resparam.res); // モジュールの設定を反映 updateSettings(clock, mStatusBarClockSettings); // 設定変更をリアルタイムで反映するためのレシーバーを登録 clock.getContext() .registerReceiver( new StatusBarClockSettingChangedReceiver( clock, mStatusBarClockSettings), new IntentFilter( Const.ACTION_STATUS_BAR_CLOCK_SETTING_CHANGED)); } }); } /** * 設定変更を再起動せず反映するためのレシーバー. */ private static class StatusBarClockSettingChangedReceiver extends com.nagopy.android.xposed.SettingChangedReceiver { private WeakReference<TextView> mClockView; public StatusBarClockSettingChangedReceiver(TextView clock, ModStatusBarClockSettingsGen settings) { super(settings, Const.ACTION_STATUS_BAR_CLOCK_SETTING_CHANGED); mClockView = new WeakReference<TextView>(clock); } @Override protected void onDataChanged() { TextView clockView = mClockView.get(); Object dataObj = dataObject.get(); if (isNotNull(dataObj, clockView)) { // 設定変更反映、表示更新 updateSettings(clockView, (ModStatusBarClockSettingsGen) dataObj); updateClock(clockView); } } } /** * 時計の表示設定を変更する. * * @param clock {@link TextView}(Clockクラスのインスタンス) * @param clockModDao {@link GenModStatusBarClockDao} */ private static void updateSettings(TextView clock, ModStatusBarClockSettingsGen clockModDao) { if (clockModDao.masterModStatusBarEnable) { // モジュール有効 // 文字サイズ clock.setTextSize(TypedValue.COMPLEX_UNIT_PX, clockModDao.statusBarClockTextSize / 100f * clockModDao.defaultStatusBarClockTextSize); // 色 clock.setTextColor(clockModDao.statusBarClockTextColor); // 配置 int gravity = getGravityFromSettings(clockModDao); clock.setGravity(gravity); // フォント clock.setTypeface(FontListPreference.makeTypeface( clockModDao.moduleResources.getAssets(), clockModDao.statusBarClockTypefaceKbn, clockModDao.statusBarClockTypefaceName, clockModDao.statusBarClockTypefaceStyle)); // 複数行を可能に clock.setSingleLine(false); // フォーマットを作成し、ADDITIONAL_FIELD_FORMATでセット String mClockFormatString = clockModDao.statusBarClockFormat; Locale locale = clockModDao.statusBarClockForceEnglish ? Locale.ENGLISH : Locale.getDefault(); final SimpleDateFormat mClockFormat = new SimpleDateFormat( mClockFormatString, locale); XposedHelpers.setAdditionalInstanceField(clock, Const.ADDITIONAL_DATE_FORMAT, mClockFormat); // 表示位置 Object currentPosition = clock.getTag(R.id.tag_clock_current_position); if (clockModDao.statusBarClockPosition.equals(Const.SB_CLOCK_POSITION_CENTER)) { if (currentPosition == null || !currentPosition.equals(Const.SB_CLOCK_POSITION_CENTER)) { LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); params.gravity = Gravity.CENTER; ViewGroup viewSystemIconArea = (ViewGroup) clock.getParent(); FrameLayout viewStatusBar = (FrameLayout) ((ViewHolder) clock .getTag(R.id.tag_status_bar_clock_view_holder)).mStatusBarView; viewSystemIconArea.removeView(clock); viewStatusBar.addView(clock, params); clock.setTag(R.id.tag_clock_current_position, Const.SB_CLOCK_POSITION_CENTER); } } else if (clockModDao.statusBarClockPosition.equals(Const.SB_CLOCK_POSITION_LEFT)) { // 左表示 if (currentPosition == null || !currentPosition.equals(Const.SB_CLOCK_POSITION_LEFT)) { // 追加用LayoutParams作成 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); params.gravity = Gravity.CENTER_VERTICAL; params.rightMargin = DimenUtil.getPixelFromDp(clock.getContext(), 6); // 親View取得 ViewGroup viewSystemIconArea = (ViewGroup) clock.getParent(); // 追加する親Viewを取得 LinearLayout viewStatusBar = (LinearLayout) ((ViewHolder) clock .getTag(R.id.tag_status_bar_clock_view_holder)).mStatusBarContents; // 現在の親Viewから削除 viewSystemIconArea.removeView(clock); // ターゲットのViewに追加 viewStatusBar.addView(clock, 0, params); // 今の状態を保存 clock.setTag(R.id.tag_clock_current_position, Const.SB_CLOCK_POSITION_LEFT); } } else if (clockModDao.statusBarClockPosition.equals(Const.SB_CLOCK_POSITION_DEFAULT)) { if (currentPosition == null || !currentPosition.equals(Const.SB_CLOCK_POSITION_DEFAULT)) { ViewGroup rootView = (ViewGroup) clock.getRootView(); int system_icon_area = clock.getContext().getResources() .getIdentifier("system_icon_area", "id", XConst.PKG_SYSTEM_UI); ViewGroup viewSystemIconArea = (ViewGroup) rootView .findViewById(system_icon_area); // Parentから削除 ViewGroup parentView = (ViewGroup) clock.getParent(); parentView.removeView(clock); // system_icon_areaに追加 viewSystemIconArea.addView(clock); clock.setTag(R.id.tag_clock_current_position, Const.SB_CLOCK_POSITION_DEFAULT); } } } else {// モジュール無効 // デフォルト値をセットし、ADDITIONAL_FIELD_FORMATを削除 clock.setTextSize(TypedValue.COMPLEX_UNIT_PX, clockModDao.defaultStatusBarClockTextSize); clock.setTextColor(clockModDao.defaultStatusBarClockTextColor); clock.setGravity(clockModDao.defaultGravity); clock.setTypeface(clockModDao.defaultTypeface); XposedHelpers.removeAdditionalInstanceField(clock, Const.ADDITIONAL_DATE_FORMAT); // 表示位置 Object currentPosition = clock.getTag(R.id.tag_clock_current_position); if (currentPosition != null && !currentPosition.equals(Const.SB_CLOCK_POSITION_DEFAULT)) { ViewGroup rootView = (ViewGroup) clock.getRootView(); int system_icon_area = clock.getContext().getResources() .getIdentifier("system_icon_area", "id", XConst.PKG_SYSTEM_UI); ViewGroup viewSystemIconArea = (ViewGroup) rootView .findViewById(system_icon_area); // Parentから削除 ViewGroup parentView = (ViewGroup) clock.getParent(); parentView.removeView(clock); // system_icon_areaに追加 viewSystemIconArea.addView(clock); clock.setTag(R.id.tag_clock_current_position, Const.SB_CLOCK_POSITION_DEFAULT); } } } private static void setViewHolder(TextView clockView) { Context context = clockView.getContext(); Resources resources = context.getResources(); int status_bar_contents = resources.getIdentifier( "status_bar_contents", "id", XConst.PKG_SYSTEM_UI); int status_bar = resources.getIdentifier( "status_bar", "id", XConst.PKG_SYSTEM_UI); View rootView = clockView.getRootView(); ViewHolder viewHolder = new ViewHolder(); viewHolder.mStatusBarView = rootView.findViewById(status_bar); viewHolder.mStatusBarContents = rootView.findViewById(status_bar_contents); clockView.setTag(R.id.tag_status_bar_clock_view_holder, viewHolder); } public static class ViewHolder { View mStatusBarView; View mStatusBarContents; } /** * 時計を更新する. * * @param clock {@link TextView}(Clockクラスのインスタンス) */ private static void updateClock(TextView clock) { XposedHelpers.callMethod(clock, "updateClock"); } /** * 設定値をGravityの値に変換して返す. * * @param settings 設定 * @return {@link Gravity} */ private static int getGravityFromSettings(ModStatusBarClockSettingsGen settings) { int gravity = Gravity.NO_GRAVITY; String horizontal = settings.statusBarClockGravityHorizontal; if (!TextUtils.isEmpty(horizontal)) { if (horizontal.equals(Const.SB_CLOCK_GRAVITY_CENTER_HORIZONTAL)) { gravity |= Gravity.CENTER_HORIZONTAL; } else if (horizontal.equals(Const.SB_CLOCK_GRAVITY_LEFT)) { gravity |= Gravity.LEFT; } else if (horizontal.equals(Const.SB_CLOCK_GRAVITY_RIGHT)) { gravity |= Gravity.RIGHT; } } String vertical = settings.statusBarClockGravityVertical; if (!TextUtils.isEmpty(vertical)) { if (vertical.equals(Const.SB_CLOCK_GRAVITY_CENTER_VERTICAL)) { gravity |= Gravity.CENTER_VERTICAL; } else if (vertical.equals(Const.SB_CLOCK_GRAVITY_TOP)) { gravity |= Gravity.TOP; } else if (vertical.equals(Const.SB_CLOCK_GRAVITY_BOTTOM)) { gravity |= Gravity.BOTTOM; } } return gravity; } }