package tk.wasdennnoch.androidn_ify.systemui.qs;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.view.View;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import tk.wasdennnoch.androidn_ify.R;
import tk.wasdennnoch.androidn_ify.XposedHook;
import tk.wasdennnoch.androidn_ify.systemui.qs.tiles.AOSPTile;
import tk.wasdennnoch.androidn_ify.systemui.qs.tiles.AndroidN_ifyTile;
import tk.wasdennnoch.androidn_ify.systemui.qs.tiles.BaseTile;
import tk.wasdennnoch.androidn_ify.systemui.qs.tiles.BatteryTile;
import tk.wasdennnoch.androidn_ify.systemui.qs.tiles.LiveDisplayTile;
import tk.wasdennnoch.androidn_ify.systemui.qs.tiles.NekoTile;
import tk.wasdennnoch.androidn_ify.systemui.qs.tiles.PartialScreenshotTile;
import tk.wasdennnoch.androidn_ify.systemui.qs.tiles.QSTile;
import tk.wasdennnoch.androidn_ify.systemui.qs.tiles.misc.BatteryMeterDrawable;
import tk.wasdennnoch.androidn_ify.utils.ConfigUtils;
import tk.wasdennnoch.androidn_ify.utils.ResourceUtils;
import tk.wasdennnoch.androidn_ify.utils.RomUtils;
public class TilesManager {
private static final String TAG = "TilesManager";
private final Object mQSTileHost;
private final Context mContext;
static final List<String> mCustomTileSpecs = new ArrayList<>();
private final Map<String, BaseTile> mTiles = new HashMap<>();
private String mCreateTileViewTileKey;
private List<String> mSecureTiles;
public boolean useVolumeTile = false;
static boolean enableNeko = false;
static {
mCustomTileSpecs.add(AndroidN_ifyTile.TILE_SPEC);
mCustomTileSpecs.add(BatteryTile.TILE_SPEC);
mCustomTileSpecs.add(NekoTile.TILE_SPEC);
mCustomTileSpecs.add(PartialScreenshotTile.TILE_SPEC);
if (RomUtils.isCm() && ConfigUtils.M && !ConfigUtils.qs().alternative_qs_loading)
mCustomTileSpecs.add(LiveDisplayTile.TILE_SPEC);
}
static public void setNekoEnabled(boolean enabled) {
enableNeko = enabled;
}
static int getLabelResource(String spec) throws Exception {
if (!mCustomTileSpecs.contains(spec))
throw new Exception("No label for spec '" + spec + "'!");
switch (spec) {
case AndroidN_ifyTile.TILE_SPEC:
return R.string.app_name;
case BatteryTile.TILE_SPEC:
return R.string.battery;
case LiveDisplayTile.TILE_SPEC:
return R.string.live_display;
case NekoTile.TILE_SPEC:
return R.string.default_tile_name;
case PartialScreenshotTile.TILE_SPEC:
return R.string.partial_screenshot;
}
return 0;
}
@SuppressWarnings("deprecation")
static Drawable getIcon(Context context, String spec) throws Exception {
switch (spec) {
case AndroidN_ifyTile.TILE_SPEC:
return ResourceUtils.getInstance(context).getDrawable(R.drawable.ic_stat_n);
case BatteryTile.TILE_SPEC:
BatteryMeterDrawable batteryMeterDrawable = new BatteryMeterDrawable(context, new Handler(), context.getResources().getColor(
context.getResources().getIdentifier("batterymeter_frame_color", "color", XposedHook.PACKAGE_SYSTEMUI)));
batteryMeterDrawable.setLevel(100);
batteryMeterDrawable.onPowerSaveChanged(false);
batteryMeterDrawable.setShowPercent(false);
return batteryMeterDrawable;
case NekoTile.TILE_SPEC:
Drawable icon = ResourceUtils.getInstance(context).getDrawable(R.drawable.stat_icon);
icon.setTint(0x4DFFFFFF);
return icon;
case PartialScreenshotTile.TILE_SPEC:
return ResourceUtils.getInstance(context).getDrawable(R.drawable.ic_crop);
default:
throw new Exception("No icon for spec '" + spec + "'!");
}
}
TilesManager(Object qsTileHost) {
mQSTileHost = qsTileHost;
mContext = (Context) XposedHelpers.callMethod(mQSTileHost, "getContext");
hook();
}
List<String> getCustomTileSpecs() {
return mCustomTileSpecs;
}
private QSTile createTileInternal(String key) {
switch (key) {
case AndroidN_ifyTile.TILE_SPEC:
return new AndroidN_ifyTile(this, mQSTileHost, key);
case BatteryTile.TILE_SPEC:
return new BatteryTile(this, mQSTileHost, key);
case LiveDisplayTile.TILE_SPEC:
return new LiveDisplayTile(this, mQSTileHost, key);
case NekoTile.TILE_SPEC:
return new NekoTile(this, mQSTileHost, key);
case PartialScreenshotTile.TILE_SPEC:
return new PartialScreenshotTile(this, mQSTileHost, key);
}
return new QSTile(this, mQSTileHost, key);
}
BaseTile createTile(String key) {
BaseTile tile = createTileInternal(key);
tile.setSecure(mSecureTiles != null && mSecureTiles.contains(key));
return tile;
}
AOSPTile createAospTile(Object tileHost, String tileSpec) {
AOSPTile tile = new AOSPTile(this, tileHost, tileSpec);
tile.setSecure(mSecureTiles != null && mSecureTiles.contains(tileSpec));
return tile;
}
public synchronized void registerTile(BaseTile tile) {
if (tile == null)
return;
String key = tile.getKey();
if (!mTiles.containsKey(key))
mTiles.put(key, tile);
}
public synchronized void unregisterTile(BaseTile tile) {
if (tile == null)
return;
String key = tile.getKey();
if (mTiles.containsKey(key))
mTiles.remove(key);
}
private void hook() {
try {
ClassLoader classLoader = mContext.getClassLoader();
Class hookClass;
try {
hookClass = XposedHelpers.findClass(QSTile.CLASS_INTENT_TILE, classLoader);
} catch (Throwable ignore) {
try {
hookClass = XposedHelpers.findClass(QSTile.CLASS_VOLUME_TILE, classLoader);
XposedHook.logI(TAG, "Using volume tile for custom tiles");
useVolumeTile = true;
} catch (Throwable t) {
XposedHook.logE(TAG, "Couldn't find required tile class, aborting hook", null);
return;
}
}
XposedHelpers.findAndHookMethod(hookClass, "handleUpdateState",
QSTile.CLASS_TILE_STATE, Object.class, new XC_MethodHook() {
@SuppressWarnings("SuspiciousMethodCalls")
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
final BaseTile tile = mTiles.get(XposedHelpers.getAdditionalInstanceField(param.thisObject, QSTile.TILE_KEY_NAME));
if (tile != null) {
tile.handleUpdateState(param.args[0], param.args[1]);
param.setResult(null);
}
}
});
XposedHelpers.findAndHookMethod(QSTile.CLASS_QS_TILE, classLoader, "createTileView",
Context.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
mCreateTileViewTileKey = (String) XposedHelpers.getAdditionalInstanceField(param.thisObject, QSTile.TILE_KEY_NAME);
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
if (mCreateTileViewTileKey == null) return;
final BaseTile tile = mTiles.get(mCreateTileViewTileKey);
if (tile != null)
tile.onCreateTileView((View) param.getResult());
mCreateTileViewTileKey = null;
}
});
XposedHelpers.findAndHookMethod(QSTile.CLASS_TILE_VIEW, classLoader, "createIcon",
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
if (mCreateTileViewTileKey == null) return;
final BaseTile tile = mTiles.get(mCreateTileViewTileKey);
if (tile != null) {
View icon = tile.onCreateIcon();
if (icon != null)
param.setResult(icon);
}
}
});
XposedHelpers.findAndHookMethod(QSTile.CLASS_QS_TILE, classLoader, "handleDestroy",
new XC_MethodHook() {
@SuppressWarnings("SuspiciousMethodCalls")
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
final BaseTile tile = mTiles.get(XposedHelpers.getAdditionalInstanceField(param.thisObject, QSTile.TILE_KEY_NAME));
if (tile != null)
tile.handleDestroy();
}
});
XposedHelpers.findAndHookMethod(QSTile.CLASS_QS_TILE, classLoader, "getDetailAdapter",
new XC_MethodHook() {
@SuppressWarnings("SuspiciousMethodCalls")
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
final BaseTile tile = mTiles.get(XposedHelpers.getAdditionalInstanceField(param.thisObject, QSTile.TILE_KEY_NAME));
if (tile != null)
param.setResult(tile.getDetailAdapter());
}
});
Method clickMethod;
try {
clickMethod = XposedHelpers.findMethodExact(hookClass, "handleClick");
} catch (Throwable t) { // PA
clickMethod = XposedHelpers.findMethodExact(hookClass, "handleToggleClick");
XposedHelpers.findAndHookMethod(hookClass, "handleClick", boolean.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
param.args[0] = true; // So that handleToggleClick gets called
}
});
}
XposedBridge.hookMethod(clickMethod, new XC_MethodHook() {
@SuppressWarnings("SuspiciousMethodCalls")
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
final BaseTile tile = mTiles.get(XposedHelpers.getAdditionalInstanceField(param.thisObject, QSTile.TILE_KEY_NAME));
if (tile != null) {
tile.handleClickInner();
param.setResult(null);
}
}
});
try {
Method longClickMethod;
try {
longClickMethod = XposedHelpers.findMethodExact(hookClass, "handleLongClick");
} catch (Throwable t) { // PA
longClickMethod = XposedHelpers.findMethodExact(hookClass, "handleDetailClick");
}
XposedBridge.hookMethod(longClickMethod, new XC_MethodHook() {
@SuppressWarnings("SuspiciousMethodCalls")
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
final BaseTile tile = mTiles.get(XposedHelpers.getAdditionalInstanceField(param.thisObject, QSTile.TILE_KEY_NAME));
if (tile != null) {
tile.handleLongClick();
param.setResult(null);
}
}
});
} catch (Throwable t) {
XposedHook.logW(TAG, "No long click method found! Custom tiles won't recognize long clicks.");
}
XposedHelpers.findAndHookMethod(hookClass, "setListening",
boolean.class, new XC_MethodHook() {
@SuppressWarnings("SuspiciousMethodCalls")
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
final BaseTile tile = mTiles.get(XposedHelpers.getAdditionalInstanceField(param.thisObject, QSTile.TILE_KEY_NAME));
if (tile != null) {
tile.setListening((boolean) param.args[0]);
param.setResult(null);
}
}
});
XposedHelpers.findAndHookMethod(QSTile.CLASS_RESOURCE_ICON, classLoader, "getDrawable",
Context.class, new XC_MethodHook() {
@SuppressWarnings("SuspiciousMethodCalls")
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
final BaseTile tile = mTiles.get(XposedHelpers.getAdditionalInstanceField(param.thisObject, QSTile.TILE_KEY_NAME));
if (tile != null && tile instanceof QSTile) {
param.setResult(((QSTile) tile).getResourceIconDrawable());
}
}
});
try { // Fix a SystemUI crash caused by this tile
XposedBridge.hookAllMethods(XposedHelpers.findClass(QSTile.CLASS_VISUALIZER_TILE, classLoader), "handleUpdateState", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
if (XposedHelpers.getObjectField(param.thisObject, "mVisualizer") == null)
param.setResult(null);
}
});
} catch (Throwable ignore) {
}
} catch (Throwable t) {
XposedHook.logE(TAG, "Error in hook", t);
}
}
void onSecureTilesChanged(List<String> secureTiles) {
mSecureTiles = secureTiles;
String s = "";
for (String sp : mSecureTiles) s += sp;
XposedHook.logD(TAG, "onSecureTilesChanged called with specs: " + s);
for (Map.Entry entry : mTiles.entrySet()) {
BaseTile tile = (BaseTile) entry.getValue();
if (tile != null) {
tile.setSecure(mSecureTiles.contains(tile.getKey()));
}
}
}
}