新增上报求助功能

This commit is contained in:
tongchenfei
2020-04-10 20:31:11 +08:00
parent a88291edf9
commit 3c03f9c4c4
13 changed files with 493 additions and 27 deletions

View File

@@ -28,6 +28,10 @@ public class ShareControl implements IShareControl {
(IMogoServiceApis) ARouter.getInstance().build(MogoServicePaths.PATH_SERVICE_APIS).navigation(context);
}
public IMogoServiceApis getMogoServiceApis(){
return mogoServiceApis;
}
public static ShareControl getInstance(Context context) {
if (sInstance == null) {
synchronized (ShareControl.class) {

View File

@@ -0,0 +1,23 @@
package com.mogo.module.share
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.mogo.module.share.constant.ShareConstants
/**
* 用于接收唤醒词指令
*/
class ShareVoiceCmdReceiver:BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
// todo 接收唤醒词指令
// todo 开启服务,准备上报求助
context?.let {
val seekHelp = Intent(it, VoiceCmdService::class.java)
seekHelp.putExtra(ShareConstants.VOICE_CMD_SERVICE_EVENT_KEY, ShareConstants.VOICE_CMD_SERVICE_SEEK_HELP)
it.startService(seekHelp)
}
}
}

View File

@@ -0,0 +1,56 @@
package com.mogo.module.share
import android.app.Service
import android.content.Intent
import android.os.IBinder
import com.mogo.module.share.constant.ShareConstants
import com.mogo.module.share.manager.ISeekHelpListener
import com.mogo.module.share.manager.SeekHelpManager
import com.mogo.utils.logger.Logger
/**
* 用来处理唤醒词
*
* @author tongchenfei
*/
class VoiceCmdService:Service() {
companion object{
private const val TAG = "VoiceCmdService"
}
private val seekListener = object : ISeekHelpListener{
override fun onSeekHelpSuccess() {
// 上报完成,关掉自己
Logger.d(TAG,"上报完成,成功")
stopSelf()
}
override fun onSeekHelpFail() {
// 上报完成,关掉自己
Logger.d(TAG,"上报完成,失败")
stopSelf()
}
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
intent?.let {
if (intent.getIntExtra(ShareConstants.VOICE_CMD_SERVICE_EVENT_KEY,0) == ShareConstants.VOICE_CMD_SERVICE_SEEK_HELP) {
// 收到语音指令,准备上报求助
Logger.i(TAG, "收到语音指令,准备上报求助")
SeekHelpManager.seekHelp(this,seekListener)
}
}
return super.onStartCommand(intent, flags, startId)
}
override fun onDestroy() {
super.onDestroy()
Logger.d(TAG, "onDestroy")
SeekHelpManager.removeSeekHelpListener(seekListener)
}
}

View File

@@ -0,0 +1,6 @@
package com.mogo.module.share.bean
/**
* 求助记录recordTime建议使用SystemClock.elapsedRealtime记录因为此时间主要是为了做时间差而不是精确时间
*/
data class SeekRecord(val recordTime: Long)

View File

@@ -0,0 +1,21 @@
package com.mogo.module.share.constant
import com.mogo.commons.debug.DebugConfig
class HttpConstant {
companion object {
const val HOST_DEV = "http://dzt-test.zhidaohulian.com"
const val HOST_TEST = "http://dzt-test.zhidaohulian.com"
const val HOST_PRODUCT = "https://dzt.zhidaohulian.com"
fun getNetHost(): String {
return when (DebugConfig.getNetMode()) {
DebugConfig.NET_MODE_DEV -> HOST_DEV
DebugConfig.NET_MODE_QA -> HOST_TEST
else -> HOST_PRODUCT
}
}
}
}

View File

@@ -15,4 +15,7 @@ public class ShareConstants {
public static final String LAUNCHER_SHARE_CLICK = "Launcher_Share_Click";
//
public static final String CARNET_USER_UPLOAD = "CarNet_user_upload";
public static final String VOICE_CMD_SERVICE_EVENT_KEY = "type";
public static final int VOICE_CMD_SERVICE_SEEK_HELP = 1;
}

View File

@@ -9,15 +9,16 @@ import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.alibaba.android.arouter.launcher.ARouter;
import com.mogo.module.share.R;
import com.mogo.module.share.ShareControl;
import com.mogo.module.share.constant.ShareConstants;
import com.mogo.service.MogoServicePaths;
import com.mogo.module.share.manager.ISeekHelpListener;
import com.mogo.module.share.manager.SeekHelpManager;
import com.mogo.service.analytics.IMogoAnalytics;
import com.mogo.utils.WindowUtils;
import com.mogo.utils.logger.Logger;
@@ -38,77 +39,99 @@ public class LaucherShareDialog implements View.OnClickListener {
private boolean isShown = false;
private TextView txtOk;
private RelativeLayout mBlockLayout;
private RelativeLayout mOilPriceLayout;
private RelativeLayout mTrafficCheckLayout;
private RelativeLayout mRoadClosureLayout;
private TextView tvBlock;
private TextView tvTrafficCheck;
private TextView tvClosure;
private TextView tvNeedHelp;
private Context mContext;
private IMogoAnalytics mAnalytics;
private WindowManager windowManager;
private WindowManager.LayoutParams layoutParams;
public LaucherShareDialog(@NonNull Context context) {
this.mContext = context;
mAnalytics = (IMogoAnalytics) ARouter.getInstance().build(MogoServicePaths.PATH_UTILS_ANALYTICS).navigation(mContext);
mAnalytics = ShareControl.getInstance(context).getMogoServiceApis().getAnalyticsApi();
}
private View body;
private void initView() {
Logger.d(TAG, "test-------3");
body = LayoutInflater.from(mContext).inflate(R.layout.launcher_dialog_share, null);
body = LayoutInflater.from(mContext).inflate(R.layout.launcher_dialog_share_2, null);
body.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
txtOk = body.findViewById(R.id.btn_share_title);
mBlockLayout = body.findViewById(R.id.btn_block_layout);
mOilPriceLayout = body.findViewById(R.id.oil_price_layout);
mTrafficCheckLayout = body.findViewById(R.id.traffic_check_layout);
mRoadClosureLayout = body.findViewById(R.id.road_closure_layout);
tvBlock = body.findViewById(R.id.tvBlock);
tvTrafficCheck = body.findViewById(R.id.tvTrafficCheck);
tvClosure = body.findViewById(R.id.tvClosure);
tvNeedHelp = body.findViewById(R.id.tvNeedHelp);
}
private void initListener() {
mBlockLayout.setOnClickListener(this);
mOilPriceLayout.setOnClickListener(this);
mTrafficCheckLayout.setOnClickListener(this);
mRoadClosureLayout.setOnClickListener(this);
tvBlock.setOnClickListener(this);
tvNeedHelp.setOnClickListener(this);
tvTrafficCheck.setOnClickListener(this);
tvClosure.setOnClickListener(this);
}
@Override
public void onClick(View view) {
int id = view.getId();
Logger.d(TAG, "onClick: " + id);
if (id == R.id.btn_block_layout) { //拥堵
if (id == R.id.tvBlock) {
//拥堵
traceTanluData("1");
sendShareReceiver("1");
traceTypeData("1");
dismiss();
} else if (id == R.id.oil_price_layout) {
// 分享油价,入口被屏蔽了
traceData("1");
Intent intent = new Intent();
intent.setData(Uri.parse("freshthing://com.zhidao.fresh.things/shareOilPrice"));
mContext.startActivity(intent);
traceTypeData("2");
dismiss();
} else if (id == R.id.traffic_check_layout) { //交通检查
} else if (id == R.id.tvTrafficCheck) {
//交通检查
traceData("1");
sendShareReceiver("2");
traceTypeData("3");
dismiss();
} else if (id == R.id.road_closure_layout) { //封路
} else if (id == R.id.tvClosure) {
//封路
traceData("1");
sendShareReceiver("3");
traceTypeData("4");
dismiss();
} else if (id == R.id.tvNeedHelp) {
// 故障求助
SeekHelpManager.INSTANCE.seekHelp(mContext,seekListener);
}
}
private ISeekHelpListener seekListener = new ISeekHelpListener() {
@Override
public void onSeekHelpSuccess() {
Logger.d(TAG,"上报求助完成,成功");
SeekHelpManager.INSTANCE.removeSeekHelpListener(seekListener);
dismiss();
}
@Override
public void onSeekHelpFail() {
Logger.d(TAG,"上报求助完成,失败");
SeekHelpManager.INSTANCE.removeSeekHelpListener(seekListener);
dismiss();
}
};
/**
* 发送广播 1拥堵2交通检查3封路
*/
@@ -156,20 +179,21 @@ public class LaucherShareDialog implements View.OnClickListener {
Logger.d(TAG,"使用windowManager实现");
if (!isShown) {
windowManager = (WindowManager) mContext.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
layoutParams = new WindowManager.LayoutParams();
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
layoutParams.format = PixelFormat.TRANSLUCENT;
layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
layoutParams.gravity = Gravity.START | Gravity.TOP;
// mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
// FLAG_LAYOUT_IN_SCREEN将window放置在整个屏幕之内,无视其他的装饰(比如状态栏) FLAG_NOT_TOUCH_MODAL不阻塞事件传递到后面的窗口
layoutParams.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
layoutParams.width = WindowUtils.getScreenWidth(mContext);
layoutParams.height = WindowUtils.getScreenHeight(mContext);
layoutParams.dimAmount = 0;//后面变暗区域透明...
//后面变暗区域透明...
layoutParams.dimAmount = 0;
layoutParams.x = 0;
layoutParams.y = 0;
initView();

View File

@@ -0,0 +1,16 @@
package com.mogo.module.share.manager
/**
* 寻求帮助结果监听
*/
interface ISeekHelpListener {
/**
* 寻求帮助成功
*/
fun onSeekHelpSuccess()
/**
* 寻求帮助失败
*/
fun onSeekHelpFail()
}

View File

@@ -0,0 +1,173 @@
package com.mogo.module.share.manager
import android.annotation.SuppressLint
import android.content.Context
import android.os.SystemClock
import android.widget.Toast
import com.alibaba.android.arouter.launcher.ARouter
import com.mogo.commons.data.BaseData
import com.mogo.commons.network.SubscribeImpl
import com.mogo.commons.voice.AIAssist
import com.mogo.commons.voice.IMogoVoiceCmdCallBack
import com.mogo.module.share.ShareControl
import com.mogo.module.share.bean.SeekRecord
import com.mogo.module.share.constant.HttpConstant
import com.mogo.module.share.net.ShareApiService
import com.mogo.service.IMogoServiceApis
import com.mogo.service.MogoServicePaths
import com.mogo.utils.logger.Logger
import com.mogo.utils.network.RequestOptions
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
/**
* 故障求助管理类,相关故障求助操作的具体实现类
* @author tongchenfei
*/
@SuppressLint("StaticFieldLeak")
object SeekHelpManager {
const val TAG = "SeekHelpManager"
/**
* 发起求助记录的限制时间,也就是只关注这段时间内的求助记录
*/
private const val SEEK_RECORD_LIMIT_TIME = 10 * 60 * 1000
/**
* 限制时间内,发起求助记录的限制次数
*/
private const val SEEK_RECORD_LIMIT_AMOUNT = 2
const val VOICE_WILL_SEEK_HELP = "将发布故障求助"
private var context:Context? = null
private var aiAssist:AIAssist? = null
private val seekRecordList = mutableListOf<SeekRecord>()
private val seekListenerList = mutableListOf<ISeekHelpListener>()
private var isSeekHelp = false
private val voiceCallback = object : IMogoVoiceCmdCallBack {
override fun onCmdSelected(cmd: String?) {
}
override fun onCmdAction(speakText: String?) {
}
override fun onCmdCancel(speakText: String?) {
}
override fun onSpeakEnd(speakText: String?) {
speakText?.let {
Logger.d(TAG, "onSpeakEnd: $it")
if (it == VOICE_WILL_SEEK_HELP) {
// 请求帮助语音播放完成,开始真正寻求帮助
realSeekHelp()
}
}
}
override fun onSpeakSelectTimeOut(speakText: String?) {
}
}
/**
* 寻求帮助,是开始故障求助的入口
* 由于当前需求仅需要提供这一个方法所以context的初始化放到了此方法后面如果有增加需要把这部分提出去
*/
fun seekHelp(context: Context, seekHelpListener: ISeekHelpListener) {
// context初始化
if (this.context == null) {
this.context = context
aiAssist = AIAssist.getInstance(context)
}
Logger.d(TAG, "开始故障求助上报---${isSeekHelp}")
seekListenerList.add(seekHelpListener)
when {
isSeekHelp -> {
// 正在求助中,进行异常提示
toast("已发布故障求助,请耐心等待")
aiAssist?.speakTTSVoice("已发布故障求助,请耐心等待")
}
getSeekAmountByLimitTime() >= SEEK_RECORD_LIMIT_AMOUNT -> {
// 超过限制时间内的限制次数,进行异常提示
toast("已在求助状态,请勿连续发布哦")
aiAssist?.speakTTSVoice("已在求助状态,请勿连续发布哦")
}
else -> {
// 没有异常情况,开始故障求助
aiAssist?.speakTTSVoice(VOICE_WILL_SEEK_HELP, voiceCallback)
}
}
}
fun removeSeekHelpListener(seekHelpListener: ISeekHelpListener) {
seekListenerList.remove(seekHelpListener)
}
private fun toast(msg: String) {
Toast.makeText(context, msg, Toast.LENGTH_LONG).show()
}
/**
* 获得限制时间 {@link SEEK_RECORD_LIMIT_TIME} 内的请求次数现在的请求记录是保存在内存中的如果遇到Launcher的Crash此记录会被重置
* 此方法就是把seekRecordList里面超过限制时间的选项去掉然后剩下的就是时限内的请求次数
*
* @return 规定时间内的请求次数
*/
private fun getSeekAmountByLimitTime(): Int {
val current = SystemClock.elapsedRealtime()
val recordIterator = seekRecordList.iterator()
while (recordIterator.hasNext()) {
if ((current - recordIterator.next().recordTime) >= SEEK_RECORD_LIMIT_TIME) {
recordIterator.remove()
}
}
return seekRecordList.size
}
/**
* 真正开始寻求帮助,实际就是请求接口
*/
private fun realSeekHelp() {
Logger.d(TAG, "realSeekHelp")
// 请求故障求助接口
ShareControl.getInstance(context).mogoServiceApis.networkApi.create(ShareApiService::class.java, HttpConstant.getNetHost()).sendHelpSignal().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(object : SubscribeImpl<BaseData>(RequestOptions.create(context)) {
override fun onSuccess(o: BaseData?) {
super.onSuccess(o)
// todo 接口请求成功需要同步v2x状态通知adas改变自车图标
isSeekHelp = true
seekRecordList.add(SeekRecord(SystemClock.elapsedRealtime()))
seekListenerList.forEach {
it.onSeekHelpSuccess()
}
}
override fun onError(e: Throwable) {
super.onError(e)
// 接口请求失败
Logger.e(TAG, "上报求助失败,网络异常")
e.printStackTrace()
seekHelpFail()
}
override fun onError(message: String?, code: Int) {
super.onError(message, code)
// 接口请求失败
Logger.e(TAG, "上报求助失败")
seekHelpFail()
}
})
}
private fun seekHelpFail() {
isSeekHelp = false
seekListenerList.forEach {
it.onSeekHelpFail()
}
toast("求助上报失败,请稍后重试")
aiAssist?.speakTTSVoice("求助上报失败,请稍后重试")
}
}

View File

@@ -0,0 +1,17 @@
package com.mogo.module.share.net
import com.mogo.commons.data.BaseData
import io.reactivex.Observable
import retrofit2.http.POST
/**
* 分享用到的api接口
*/
interface ShareApiService {
/**
* 发起求助接口
*/
@POST("")
fun sendHelpSignal(): Observable<BaseData>
}