观察者模式QS快捷开关控制状态栏ICON显示

2 篇文章 0 订阅
2 篇文章 0 订阅

本文我们在QS下拉快捷开关添加一个"高铁模式"的按钮(实际功能只是模拟开启振动模式),点击后控制状态栏右上角ICON图标去显示一个"高铁icon"(类似飞行模型的飞机icon)。
首先添加状态栏"高铁模式"的icon
SystemUI的config.xml文件里面添加item项,见 注释为添加代码

<string-array name="config_statusBarIcons">
        <item><xliff:g id="id">@string/status_bar_vpn</xliff:g></item>
        <item><xliff:g id="id">@string/status_bar_train</xliff:g></item>  <!--jiaxian-->
    <item><xliff:g id="id">@string/status_bar_bluetooth</xliff:g></item>
    <string translatable="false" name="status_bar_train">train</string>    <!--jiaxian-->

然后回到PhoneStatusBarPolicy.java中,控制其显示

public class PhoneStatusBarPolicy
        implements BluetoothController.Callback,
                CommandQueue.Callbacks,
                RotationLockControllerCallback,
                Listener,
                ZenModeController.Callback,
                DeviceProvisionedListener,
                KeyguardStateController.Callback,
                LocationController.LocationChangeCallback,
                RecordingController.RecordingStateChangeCallback,
                TrainController.Callback { //jiaxian add
            ...
            private final TrainController mTrainController; 
            ...
            private final String mSlotTrain;//jiaxian add
            ...
    @Inject
    public PhoneStatusBarPolicy(StatusBarIconController iconController,
                                ...
        CommandQueue commandQueue, TrainController trainController){ //jiaxian add
        ...    
        mTrainController = trainController; //jiaxian add
        ...
        mSlotTrain = resources.getString(com.android.internal.R.string.status_bar_train);//jiaxian add
        
    }
            ...
     /** Initialize the object after construction. */
    public void init() {
        // listen for broadcasts jiaxian add begin
        mIconController.setIcon(mSlotTrain, R.drawable.stat_sys_train, null);
        //先设置为false,不显示,后期是否为true的显示由QS设置后,通过回调函数来改变
        mIconController.setIconVisibility(mSlotTrain, false);
        //把PhoneStatusBarPolicy加入回调函数中
        mTrainController.addCallback(this);
       //jiaxian add end
    } 
   //jiaxian add begin
   //实现TrainController.Callback接口后,重写回调方法onTrainStateChange ,
   //当在trainControllerImpl执行notifyAllChanged的时候,该方法就会被执行回调调用。         
   //回调传过来的enabled参数即是控制是否显示的参数。
    @Override
    public void onTrainStateChange(boolean enabled) {
        Log.d("PhoneStatusBarPolicy","onTrainStateChange:"+enabled);
        updateTrain();
    }
    //jiaxian train
    private final void updateTrain() {
        boolean trainVisible = false;
        //这里通过调用isTrainEnabled()来获取状态,其实也可以将onTrainStateChange的形参enabled
        //传过来,然后再传入setIconVisibility中
        trainVisible = mTrainController.isTrainEnabled();
        Log.d("PhoneStatusBarPolicy","updateTrain:"+trainVisible);
        mIconController.setIcon(mSlotTrain, R.drawable.stat_sys_train, null);
        mIconController.setIconVisibility(mSlotTrain, trainVisible);
    }
}
   //jiaxian add end

添加QS快捷开关的流程参考之前写的文章:https://blog.csdn.net/z1804362542/article/details/126126003?spm=1001.2014.3001.5502
这里我们集中讲解需要我们自己创建的TrainTile.java类、TrainController类、TrainControllerImpl类所做的功能
重要的代码在于
1、trainController.observe(this, mCallback); 这个做的就是把当前的TrainTile类注册进回调里面,因为observe走下去其实就后会执行到addCallback(this),这个就是和上面PhoneStatusBarPolicy.java出现的一样了,PhoneStatusBarPolicy也是把自己addCallback(this)进去。
2、final boolean train = trainController.isTrainEnabled();通过trainController的方法来控制目前是显示还是隐藏的状态,这个方法TrainTile用到,PhoneStatusBarPolicy也会用到,这样大家才能一起同步目前的状态。而如何同步的关键在于,回调函数发送作用了。
3、

private final TrainController.Callback mCallback = new TrainController.Callback() { 
        @Override
        public void onTrainStateChange(boolean enabled) {
            refreshState(enabled);
        }
    };

这个是写在TrainTile方的一个回调方法,当在TrainControllerImpl的notifyAllChanged()通知触发的时候,这个回到就会被执行,然后回调修改状态,在PhoneStatusBarPolicy中就是我们的重写的onTrainStateChange方法,区别是PhoneStatusBarPolicy是通过实现implments TrainController.addCallback来实现的,而我这里是直接new TrainController.Callback然后匿名内部类实现的

/*
 * Copyright (c) 2016, The Android Open Source Project
 * Contributed by the Paranoid Android 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.ape.systemui.qs.tiles;

import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import android.provider.Settings;
import android.content.Context;
import android.util.Log;
import android.os.UserManager;
import android.os.SystemClock;
import android.content.Intent;
import android.content.res.Resources;
import android.service.quicksettings.Tile;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import android.widget.Toast;
import android.content.Intent;
import android.media.AudioManager;
import com.android.systemui.statusbar.policy.TrainController.Callback;
import static android.media.AudioManager.RINGER_MODE_NORMAL;
import static android.media.AudioManager.RINGER_MODE_SILENT;
import static android.media.AudioManager.RINGER_MODE_VIBRATE;

import com.android.systemui.statusbar.policy.TrainController;

import com.android.systemui.R;

import javax.inject.Inject;

/** Quick settings tile: jiaxian.zhang add mute **/
public class TrainTile extends QSTileImpl<BooleanState> {
    //这个可以Intent到Settings中的音量设置页面,是Settings专门提供给我们的
    private static final Intent SOUND_SETTINGS = new Intent(Settings.ACTION_SOUND_SETTINGS);

    private final TrainController trainController;
    public static final int MSG_RINGER_MODE_SILENT = 0;  //静音模式
    public static final int MSG_RINGER_MODE_NORMAL = 2;  //一般模式

    //注解不要少加
    @Inject
    public TrainTile(QSHost host,TrainController trainController) {
        super(host);
        this.trainController = trainController;
        //在这里把TrainTile注册进去,自己成为一名观察者
        trainController.observe(this, mCallback);
    }

    @Override
    public BooleanState newTileState() {
        return new BooleanState();
    }

    @Override
    protected void handleLongClick() {
    }

    @Override
    public Intent getLongClickIntent() {
        Log.d("Mute","getLongClickIntent");
        return new Intent(Settings.ACTION_SOUND_SETTINGS);
    }

    @Override
    public boolean isAvailable() {
        return mContext.getString(R.string.quick_settings_tiles_stock).contains("train");
    }

    @Override
    protected void handleClick() {
        //当是打开时,mState.value为true。当是关闭是,mState.value为false
        //假如mState.value是false,获取后再取反,则最后setTrainEnabled();传进去的就是false
        final boolean newState = !mState.value;
        Log.d("Train","handleClick"+" mState.value:"+mState.value+" newState:"+newState);
        trainController.setTrainEnabled(!newState);
        refreshState(newState);//其实在这里refreshState也可以不写,参数newState也不重要,
        //因为我们的回调onTrainStateChange最终也会去执行refreshState()
        //然后最终还是会触发到我们的handleUpdateState进行QS图标和文件的刷新
    }

    @Override
    public CharSequence getTileLabel() {
        return mContext.getString(R.string.quick_settings_mute_label);
    }

     /*这个方法的执行有2情况
    1、下划QS面板的时候触发所有可选的Tile类的handleUpdateState()执行
    2、执行refreshState()的时候,触发所在类的handleUpdateState()执行
    */
    @Override
    protected void handleUpdateState(BooleanState state, Object arg) {
        final boolean train = trainController.isTrainEnabled();
         //trainController.isTrainEnabled();获得的就是传给setTrainEnabled的true或者false
        //所以我们这里要对它取反,以修改QS图标状态显示
        Log.d("handleUpdateStatestate","isTrainEnabled()"+train);
        state.value = !train;
        Log.d("handleUpdateStatestate","state.value"+state.value);
        state.icon = ResourceIcon.get(R.drawable.ic_train_normal);
        state.label = mContext.getString(R.string.quick_settings_mute_label);
        state.contentDescription = getTrainString();
        state.state = state.value ? Tile.STATE_INACTIVE : Tile.STATE_ACTIVE;
    }

    private String getTrainString() {
        return mContext.getString(R.string.quick_settings_mute_label);
    }

    @Override
    public int getMetricsCategory() {
        return MetricsEvent.QS_TRAIN;
    }

    @Override
    public void handleSetListening(boolean listening) {
    }

    private final TrainController.Callback mCallback = new TrainController.Callback() {
        @Override
        public void onTrainStateChange(boolean enabled) {
//监听到其他类修改了后,作为观察者的"我"回调观察到了,回调执行的更新状态,
//是谁执行了然后回调到我这里呢?执行者是在TrainControllerImpl类的notifyChanged()方法中。
//我只要被促发就执行refreshState(),refreshState会触发TrainTile类的handleUpdateState()
//方法来改变QS的图标和文本,所以我们的 refreshState(enableld) 和 refreshState()是否传参数进去区别不大,
//因为在handleUpdateState()是靠trainController来获取true、false控制区别状态的
            refreshState(enableld);
        }
    };

}

---------------------------------------------------------------------

补充,我们也可以让TrainTilePhoneStatusBarPolicy的做法一样,实现类,重写方法,不用匿名内部类的实现方式
    public class TrainTile extends QSTileImpl<BooleanState> 
    implements TrainController.Callback {
        
 
    @Inject
    public TrainTile(QSHost host,TrainController trainController) {
        super(host);
        this.trainController = trainController;
//      trainController.observe(this, mCallback);改为:
        trainController.addCallback(this);
    }
        

//    private final TrainController.Callback mCallback = new TrainController.Callback() {
//        @Override
//        public void onTrainStateChange(boolean enabled) {
//            refreshState(enabled);//监听到其他类修改了后,作为观察者的"我"回调观察到了,回调执行的更新状态,
//            //是谁执行了然后回调到我这里呢?执行者是在RotationLockControllerImpl类的notifyChanged()方法中。
//        }
//    }; 改为:

    @Override
    public void onTrainStateChange(boolean enabled) {
        //监听到其他类修改了后,作为观察者的"我"回调观察到了,回调执行的更新状态,
        //是谁执行了然后回调到我这里呢?执行者是在RotationLockControllerImpl类的notifyChanged()方法中。
        refreshState(enabled);
    }

}

现在看来TrainControllerImpl很重要,需要由它来发送通知同步状态、修改状态、获取状态。但在之前,我们需要先定义它的接口规范。TrainController继承父类CallbackController,所以TrainController是间接拥有addCallback等方法的。

package com.android.systemui.statusbar.policy;

import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.TrainController.Callback;

import java.util.Collection;
import java.util.List;

public interface TrainController extends CallbackController<Callback>{

    boolean isTrainEnabled();
    void setTrainEnabled(boolean enabled);

    public interface Callback {
        void onTrainStateChange(boolean enabled);
    }

}

下面这个类系统提供,不用我们创建,我们只是让TrainController 实现它即可:

public interface CallbackController<T> {
    void addCallback(T listener);
    void removeCallback(T listener);

    /**
     * Wrapper to {@link #addCallback(Object)} when a lifecycle is in the resumed state
     * and {@link #removeCallback(Object)} when not resumed automatically.
     */
    default T observe(LifecycleOwner owner, T listener) {
        return observe(owner.getLifecycle(), listener);
    }

    /**
     * Wrapper to {@link #addCallback(Object)} when a lifecycle is in the resumed state
     * and {@link #removeCallback(Object)} when not resumed automatically.
     */
    default T observe(Lifecycle lifecycle, T listener) {
        lifecycle.addObserver((LifecycleEventObserver) (lifecycleOwner, event) -> {
            if (event == Event.ON_RESUME) {
                addCallback(listener);
            } else if (event == Event.ON_PAUSE) {
                removeCallback(listener);
            }
        });
        return listener;
    }
}

创建实现类

package com.android.systemui.statusbar.policy;

import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;

import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.WeakHashMap;
import android.media.AudioManager;

import javax.inject.Inject;
import javax.inject.Singleton;

/**
 */
@Singleton
public class TrainControllerImpl implements TrainController{
    private static final String TAG = "TrainController";
    //这个Callback即是TrainController.Callback
    //private ArrayList<Callback> mTrainChangeCallbacks = new ArrayList<>();
    private Context mContext;
    private boolean mEnabled;
    private boolean mIsActive;
    private AudioManager mAudioManager;
    private final H mHandler;
    private int mState;

    /**
     */
    @Inject
    public TrainControllerImpl(Context context,@Main Looper mainLooper) {
        mContext = context;
        mHandler = new H(mainLooper);
        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
    }

    @Override
    public boolean isTrainEnabled() {
        Log.d("TrainController","mEnabled:"+mEnabled);
        return mEnabled;
    }

    @Override
    public void setTrainEnabled(boolean enabled) {
        Log.d("TrainController","enabled:"+enabled);
        //如果开关被点击是true,表激活,就开启振动模式
        //开启高铁模式我们做的功能是开启震动模式,关闭就是恢复正常
        if(enabling){
            mAudioManager.setRingerMode(MSG_RINGER_MODE_VIBRATE);
        }else{
            mAudioManager.setRingerMode(MSG_RINGER_MODE_NORMAL);
        }

        mEnabled = enabled;

/*通知所有观察者改状态 直接调用notifyAllChanged()这种实现形式会有一个报错:
android.view.ViewRootImpl$CalledFromWrongThreadException:
Only the original thread that created a view hierarchy can touch its views.
也就是我们现在不是在创建线程的原始线程,所以不准,解决方式是模仿一下
LocationControllerImpl的构造方法中的参数@Main Looper mainLooper
*/
        //notifyAllChanged();

        //新的可实现方式,用mHandler发送信息给原始线程H来解决。 实现成功
        mHandler.obtainMessage(H.CHANGE_ICON,isTrainEnabled()).sendToTarget();
    }


//    public void notifyAllChanged(){ 
//        for (Callback callback : mTrainChangeCallbacks) {
//            callback.onTrainStateChange(isTrainEnabled());
//        }
//    }

    @Override
    public void addCallback(Callback cb) {
        mHandler.obtainMessage(H.MSG_ADD_CALLBACK, cb).sendToTarget();
    }

    @Override
    public void removeCallback(Callback cb) {
        mHandler.obtainMessage(H.MSG_REMOVE_CALLBACK, cb).sendToTarget();
    }

    //构造创建视图的原始线程来执行notifyAllChanged操作
    private final class H extends Handler {
        private static final int MSG_ADD_CALLBACK = 3;
        private static final int MSG_REMOVE_CALLBACK = 4;
        private static final int CHANGE_ICON = 5;

        private ArrayList<TrainController.Callback> mTrainChangeCallbacks = new ArrayList<>();

        H(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_ADD_CALLBACK:
                    mTrainChangeCallbacks.add((TrainController.Callback) msg.obj);
                    break;
                case MSG_REMOVE_CALLBACK:
                    mTrainChangeCallbacks.remove((TrainController.Callback) msg.obj);
                    break;
                case CHANGE_ICON:
                    //取出 开关状态参数 传入notifyAllChanged
                    boolean isTrainEnabled = (boolean)msg.obj;
                    notifyAllChanged(isTrainEnabled);
                    break;
            }
        }

        private void notifyAllChanged(boolean isTrainEnabled) {
            for (TrainController.Callback callback : mTrainChangeCallbacks) {
                callback.onTrainStateChange(isTrainEnabled);
            }
        }
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值