步骤跟之前的添加阅读模式、距离感应和自动亮度差不多!
但有个区别是:点击图标的时候不用跟设置下的选项开关进行同步,只需要打开计算器就行。
重点:
package com.android.systemui.qs.tiles;import android.util.Log;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import android.content.Intent;
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.widget.Switch;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import android.os.SystemProperties;public class CalculatorTile extends QSTileImpl<BooleanState> {
private final Icon mIcon = ResourceIcon.get(R.drawable.ic_ldq_calculator);private boolean isAuto;public CalculatorTile(QSHost host){
super(host); }@Overridepublic BooleanState newTileState() {
return new BooleanState();}@Overrideprotected void handleClick() {
Intent intent = new Intent("com.open");mContext.sendBroadcast(intent);}@Overrideprotected void handleUpdateState(BooleanState state, Object arg) {
state.label = mContext.getString(R.string.calculator);state.icon = mIcon;//定义图标 state.value = false; state.state = Tile.STATE_INACTIVE; state.expandedAccessibilityClassName = Switch.class.getName();}//设置状态切换@Overrideprotected void handleUserSwitch(int newUserId) {
}@Overridepublic Intent getLongClickIntent() {
Log.e( "ldq","1" );Intent intent = new Intent("com.open");intent.setClassName("com.android.calculator2", "com.android.calculator2.Calculator");return intent;//长按监听}@Overridepublic void handleSetListening(boolean listening) {
}@Overridepublic boolean isAvailable() {
return true;//可见}@Overridepublic CharSequence getTileLabel() {
return mContext.getString(R.string.calculator);}//定义标题@Overridepublic int getMetricsCategory() {
return 1200;}}
这里的点击和长按都是打开计算器。
于是点击时候发送广播,在QSTileImpl.java中接收广播,接收到后打开计算器。
先注册广播接收器,在构造函数里注册好。
private Open op;protected QSTileImpl(QSHost host) {
mHost = host;mContext = host.getContext();op = new Open(); //开始注册IntentFilter intentFilter = new IntentFilter("com.open");mContext.registerReceiver( op,intentFilter );Log.e( "ldq","注册广播成功!" );}
注册完添加接收后处理的这一段代码:
public class Open extends BroadcastReceiver {
@Overridepublic void onReceive(Context context, Intent intent) {
String action = intent.getAction();if (action.equals("com.open")){
Log.e("ldq", "收到广播");intent.setClassName("com.android.calculator2", "com.android.calculator2.Calculator");Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0);}}}
贴上QSTileImpl.java全部代码:
注意!!!因为客户需求,需要屏蔽某些图标的长按功能,所以protected void handleLongClick()里面是进行过修改的!
/** Copyright (C) 2017 The Android Open Source Project** 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.android.systemui.qs.tileimpl;import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_CLICK;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_LONG_PRESS;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_SECONDARY_CLICK;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_CONTEXT;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_POSITION;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_VALUE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.metrics.LogMaker;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.service.quicksettings.Tile;
import android.text.format.DateUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
import android.provider.Settings; //ldq
import android.content.IntentFilter;
import android.content.BroadcastReceiver;import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.Prefs;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSIconView;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.qs.PagedTileLayout.TilePage;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QuickStatusBarHeader;
import com.mediatek.systemui.statusbar.util.FeatureOptions;import java.util.ArrayList;/*** Base quick-settings tile, extend this to create a new tile.** State management done on a looper provided by the host. Tiles should update state in* handleUpdateState. Callbacks affecting state should use refreshState to trigger another* state update pass on tile looper.*/
public abstract class QSTileImpl<TState extends State> implements QSTile {
protected final String TAG = "Tile." + getClass().getSimpleName();protected static final boolean DEBUG = Log.isLoggable("Tile", Log.DEBUG)|| FeatureOptions.LOG_ENABLE;private static final long DEFAULT_STALE_TIMEOUT = 10 * DateUtils.MINUTE_IN_MILLIS;protected static final Object ARG_SHOW_TRANSIENT_ENABLING = new Object();protected final QSHost mHost;protected final Context mContext;// @NonFinalForTestingprotected H mHandler = new H(Dependency.get(Dependency.BG_LOOPER));protected final Handler mUiHandler = new Handler(Looper.getMainLooper());private final ArraySet<Object> mListeners = new ArraySet<>();private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);private final ArrayList<Callback> mCallbacks = new ArrayList<>();private final Object mStaleListener = new Object();protected TState mState = newTileState();private TState mTmpState = newTileState();private boolean mAnnounceNextStateChange;private String mTileSpec;private EnforcedAdmin mEnforcedAdmin;private boolean mShowingDetail;private int mIsFullQs;public abstract TState newTileState();abstract protected void handleClick();abstract protected void handleUpdateState(TState state, Object arg);private Open op;/*** Declare the category of this tile.** Categories are defined in {@link com.android.internal.logging.nano.MetricsProto.MetricsEvent}* by editing frameworks/base/proto/src/metrics_constants.proto.*/abstract public int getMetricsCategory();protected QSTileImpl(QSHost host) {
mHost = host;mContext = host.getContext();op = new Open();IntentFilter intentFilter = new IntentFilter("com.open");mContext.registerReceiver( op,intentFilter );Log.e( "ldq","注册广播成功!" );}/*** Adds or removes a listening client for the tile. If the tile has one or more* listening client it will go into the listening state.*/public void setListening(Object listener, boolean listening) {
mHandler.obtainMessage(H.SET_LISTENING, listening ? 1 : 0, 0, listener).sendToTarget();}protected long getStaleTimeout() {
return DEFAULT_STALE_TIMEOUT;}@VisibleForTestingprotected void handleStale() {
setListening(mStaleListener, true);}public String getTileSpec() {
return mTileSpec;}public void setTileSpec(String tileSpec) {
mTileSpec = tileSpec;}public QSHost getHost() {
return mHost;}public QSIconView createTileView(Context context) {
return new QSIconViewImpl(context);}public DetailAdapter getDetailAdapter() {
return null; // optional}protected DetailAdapter createDetailAdapter() {
throw new UnsupportedOperationException();}/*** Is a startup check whether this device currently supports this tile.* Should not be used to conditionally hide tiles. Only checked on tile* creation or whether should be shown in edit screen.*/public boolean isAvailable() {
return true;}// safe to call from any threadpublic void addCallback(Callback callback) {
mHandler.obtainMessage(H.ADD_CALLBACK, callback).sendToTarget();}public void removeCallback(Callback callback) {
mHandler.obtainMessage(H.REMOVE_CALLBACK, callback).sendToTarget();}public void removeCallbacks() {
mHandler.sendEmptyMessage(H.REMOVE_CALLBACKS);}public void click() {
mMetricsLogger.write(populate(new LogMaker(ACTION_QS_CLICK).setType(TYPE_ACTION)));mHandler.sendEmptyMessage(H.CLICK);}public void secondaryClick() {
mMetricsLogger.write(populate(new LogMaker(ACTION_QS_SECONDARY_CLICK).setType(TYPE_ACTION)));mHandler.sendEmptyMessage(H.SECONDARY_CLICK);}public void longClick() {
mMetricsLogger.write(populate(new LogMaker(ACTION_QS_LONG_PRESS).setType(TYPE_ACTION)));mHandler.sendEmptyMessage(H.LONG_CLICK);Prefs.putInt(mContext,Prefs.Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT,QuickStatusBarHeader.MAX_TOOLTIP_SHOWN_COUNT);}public LogMaker populate(LogMaker logMaker) {
if (mState instanceof BooleanState) {
logMaker.addTaggedData(FIELD_QS_VALUE, ((BooleanState) mState).value ? 1 : 0);}return logMaker.setSubtype(getMetricsCategory()).addTaggedData(FIELD_CONTEXT, mIsFullQs).addTaggedData(FIELD_QS_POSITION, mHost.indexOf(mTileSpec));}public void showDetail(boolean show) {
mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();}public void refreshState() {
refreshState(null);}protected final void refreshState(Object arg) {
mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget();}public void clearState() {
mHandler.sendEmptyMessage(H.CLEAR_STATE);}public void userSwitch(int newUserId) {
mHandler.obtainMessage(H.USER_SWITCH, newUserId, 0).sendToTarget();}public void fireToggleStateChanged(boolean state) {
mHandler.obtainMessage(H.TOGGLE_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();}public void fireScanStateChanged(boolean state) {
mHandler.obtainMessage(H.SCAN_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();}public void destroy() {
mHandler.sendEmptyMessage(H.DESTROY);}public TState getState() {
return mState;}public void setDetailListening(boolean listening) {
// optional}// call only on tile worker looperprivate void handleAddCallback(Callback callback) {
mCallbacks.add(callback);callback.onStateChanged(mState);}private void handleRemoveCallback(Callback callback) {
mCallbacks.remove(callback);}private void handleRemoveCallbacks() {
mCallbacks.clear();}protected void handleSecondaryClick() {
Log.e("ldq","click");// Default to normal click.handleClick();}protected void handleLongClick() {
//长按在这里获取到对应的action,添加判断 ldq//支持wifi,蓝牙,护眼模式,投射,计算器支持长按跳转界面String Action = getLongClickIntent().getAction();if ( Action.equals( Settings.ACTION_WIFI_SETTINGS ) ){
//wifiDependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(getLongClickIntent(), 0);}else if( Action.equals( Settings.ACTION_BLUETOOTH_SETTINGS ) ) {
//蓝牙Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(getLongClickIntent(), 0);}else if( Action.equals( Settings.ACTION_NIGHT_DISPLAY_SETTINGS ) ) {
//护眼模式Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(getLongClickIntent(), 0);}else if( Action.equals( Settings.ACTION_CAST_SETTINGS ) ) {
//投射Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(getLongClickIntent(), 0);}else if( Action.equals( "com.open" ) ) {
//计算器Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(getLongClickIntent(), 0);}}public abstract Intent getLongClickIntent();protected void handleClearState() {
mTmpState = newTileState();mState = newTileState();}protected void handleRefreshState(Object arg) {
handleUpdateState(mTmpState, arg);final boolean changed = mTmpState.copyTo(mState);if (changed) {
handleStateChanged();}mHandler.removeMessages(H.STALE);mHandler.sendEmptyMessageDelayed(H.STALE, getStaleTimeout());setListening(mStaleListener, false);}private void handleStateChanged() {
boolean delayAnnouncement = shouldAnnouncementBeDelayed();if (mCallbacks.size() != 0) {
/// M: [ALPS02724123]copy value for fixing racing condition @{
// QSTileView.setIcon will use different thread to access the stateTState state = newTileState();mState.copyTo(state);for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onStateChanged(state);}/// @}if (mAnnounceNextStateChange && !delayAnnouncement) {
String announcement = composeChangeAnnouncement();if (announcement != null) {
mCallbacks.get(0).onAnnouncementRequested(announcement);}}}mAnnounceNextStateChange = mAnnounceNextStateChange && delayAnnouncement;}protected boolean shouldAnnouncementBeDelayed() {
return false;}protected String composeChangeAnnouncement() {
return null;}private void handleShowDetail(boolean show) {
mShowingDetail = show;for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onShowDetail(show);}}protected boolean isShowingDetail() {
return mShowingDetail;}private void handleToggleStateChanged(boolean state) {
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onToggleStateChanged(state);}}private void handleScanStateChanged(boolean state) {
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onScanStateChanged(state);}}protected void handleUserSwitch(int newUserId) {
handleRefreshState(null);}private void handleSetListeningInternal(Object listener, boolean listening) {
if (listening) {
if (mListeners.add(listener) && mListeners.size() == 1) {
if (DEBUG) Log.d(TAG, "handleSetListening true");handleSetListening(listening);refreshState(); // Ensure we get at least one refresh after listening.}} else {
if (mListeners.remove(listener) && mListeners.size() == 0) {
if (DEBUG) Log.d(TAG, "handleSetListening false");handleSetListening(listening);}}updateIsFullQs();}private void updateIsFullQs() {
for (Object listener : mListeners) {
if (TilePage.class.equals(listener.getClass())) {
mIsFullQs = 1;return;}}mIsFullQs = 0;}protected abstract void handleSetListening(boolean listening);protected void handleDestroy() {
if (mListeners.size() != 0) {
handleSetListening(false);}mCallbacks.clear();}protected void checkIfRestrictionEnforcedByAdminOnly(State state, String userRestriction) {
EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(mContext,userRestriction, ActivityManager.getCurrentUser());if (admin != null && !RestrictedLockUtils.hasBaseUserRestriction(mContext,userRestriction, ActivityManager.getCurrentUser())) {
state.disabledByPolicy = true;mEnforcedAdmin = admin;} else {
state.disabledByPolicy = false;mEnforcedAdmin = null;}}public abstract CharSequence getTileLabel();public static int getColorForState(Context context, int state) {
switch (state) {
case Tile.STATE_UNAVAILABLE:return Utils.getDisabled(context,Utils.getColorAttr(context, android.R.attr.textColorSecondary));case Tile.STATE_INACTIVE:return Utils.getColorAttr(context, android.R.attr.textColorSecondary);case Tile.STATE_ACTIVE:return Utils.getColorAttr(context, android.R.attr.colorPrimary);default:Log.e("QSTile", "Invalid state " + state);return 0;}}public class Open extends BroadcastReceiver {
@Overridepublic void onReceive(Context context, Intent intent) {
String action = intent.getAction();if (action.equals("com.open")){
Log.e("ldq", "收到广播");intent.setClassName("com.android.calculator2", "com.android.calculator2.Calculator");Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0);}}}protected final class H extends Handler {
private static final int ADD_CALLBACK = 1;private static final int CLICK = 2;private static final int SECONDARY_CLICK = 3;private static final int LONG_CLICK = 4;private static final int REFRESH_STATE = 5;private static final int SHOW_DETAIL = 6;private static final int USER_SWITCH = 7;private static final int TOGGLE_STATE_CHANGED = 8;private static final int SCAN_STATE_CHANGED = 9;private static final int DESTROY = 10;private static final int CLEAR_STATE = 11;private static final int REMOVE_CALLBACKS = 12;private static final int REMOVE_CALLBACK = 13;private static final int SET_LISTENING = 14;private static final int STALE = 15;@VisibleForTestingprotected H(Looper looper) {
super(looper);}@Overridepublic void handleMessage(Message msg) {
String name = null;try {
if (msg.what == ADD_CALLBACK) {
name = "handleAddCallback";handleAddCallback((QSTile.Callback) msg.obj);} else if (msg.what == REMOVE_CALLBACKS) {
name = "handleRemoveCallbacks";handleRemoveCallbacks();} else if (msg.what == REMOVE_CALLBACK) {
name = "handleRemoveCallback";handleRemoveCallback((QSTile.Callback) msg.obj);} else if (msg.what == CLICK) {
name = "handleClick";if (mState.disabledByPolicy) {
Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(mContext, mEnforcedAdmin);Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0);} else {
handleClick();}} else if (msg.what == SECONDARY_CLICK) {
name = "handleSecondaryClick";handleSecondaryClick();} else if (msg.what == LONG_CLICK) {
name = "handleLongClick";handleLongClick();} else if (msg.what == REFRESH_STATE) {
name = "handleRefreshState";handleRefreshState(msg.obj);} else if (msg.what == SHOW_DETAIL) {
name = "handleShowDetail";handleShowDetail(msg.arg1 != 0);} else if (msg.what == USER_SWITCH) {
name = "handleUserSwitch";handleUserSwitch(msg.arg1);} else if (msg.what == TOGGLE_STATE_CHANGED) {
name = "handleToggleStateChanged";handleToggleStateChanged(msg.arg1 != 0);} else if (msg.what == SCAN_STATE_CHANGED) {
name = "handleScanStateChanged";handleScanStateChanged(msg.arg1 != 0);} else if (msg.what == DESTROY) {
name = "handleDestroy";handleDestroy();} else if (msg.what == CLEAR_STATE) {
name = "handleClearState";handleClearState();} else if (msg.what == SET_LISTENING) {
name = "handleSetListeningInternal";handleSetListeningInternal(msg.obj, msg.arg1 != 0);} else if (msg.what == STALE) {
name = "handleStale";handleStale();} else {
throw new IllegalArgumentException("Unknown msg: " + msg.what);}} catch (Throwable t) {
final String error = "Error in " + name;Log.w(TAG, error, t);mHost.warn(error, t);}}}public static class DrawableIcon extends Icon {
protected final Drawable mDrawable;protected final Drawable mInvisibleDrawable;public DrawableIcon(Drawable drawable) {
mDrawable = drawable;mInvisibleDrawable = drawable.getConstantState().newDrawable();}@Overridepublic Drawable getDrawable(Context context) {
return mDrawable;}@Overridepublic Drawable getInvisibleDrawable(Context context) {
return mInvisibleDrawable;}}public static class DrawableIconWithRes extends DrawableIcon {
private final int mId;public DrawableIconWithRes(Drawable drawable, int id) {
super(drawable);mId = id;}@Overridepublic boolean equals(Object o) {
return o instanceof DrawableIconWithRes && ((DrawableIconWithRes) o).mId == mId;}}public static class ResourceIcon extends Icon {
private static final SparseArray<Icon> ICONS = new SparseArray<Icon>();protected final int mResId;private ResourceIcon(int resId) {
mResId = resId;}public static Icon get(int resId) {
Icon icon = ICONS.get(resId);if (icon == null) {
icon = new ResourceIcon(resId);ICONS.put(resId, icon);}return icon;}@Overridepublic Drawable getDrawable(Context context) {
return context.getDrawable(mResId);}@Overridepublic Drawable getInvisibleDrawable(Context context) {
return context.getDrawable(mResId);}@Overridepublic boolean equals(Object o) {
return o instanceof ResourceIcon && ((ResourceIcon) o).mResId == mResId;}@Overridepublic String toString() {
return String.format("ResourceIcon[resId=0x%08x]", mResId);}}protected static class AnimationIcon extends ResourceIcon {
private final int mAnimatedResId;public AnimationIcon(int resId, int staticResId) {
super(staticResId);mAnimatedResId = resId;}@Overridepublic Drawable getDrawable(Context context) {
// workaround: get a clean state for every new AVDreturn context.getDrawable(mAnimatedResId).getConstantState().newDrawable();}}
}