Merge branch 'feature/v1.0.4' of gitlab.zhidaoauto.com:ecos/yycp-service/Launcher into feature/v1.0.4

This commit is contained in:
wangcongtao
2020-04-14 17:08:36 +08:00
15 changed files with 533 additions and 29 deletions

2
.idea/misc.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
</project>

View File

@@ -54,7 +54,7 @@ public class MarkerNoveltyInfo {
this.sn = sn;
}
public class ContentData {
public static class ContentData {
private String content;
private String iconUrl;
private String imgUrl;
@@ -66,6 +66,8 @@ public class MarkerNoveltyInfo {
private boolean desplayHost;
private boolean fabulous;
private String styleType;
//上报类型1-用户上报2-后台上报 3-三方上报
private String uploadType;
public String getContent() {
return content;
@@ -155,6 +157,14 @@ public class MarkerNoveltyInfo {
this.fabulous = fabulous;
}
public String getUploadType() {
return uploadType;
}
public void setUploadType(String uploadType) {
this.uploadType = uploadType;
}
@Override
public String toString() {
return "ContentData{" +
@@ -169,6 +179,7 @@ public class MarkerNoveltyInfo {
", desplayHost=" + desplayHost +
", fabulous=" + fabulous +
", styleType='" + styleType + '\'' +
", uploadType='" + uploadType + '\'' +
'}';
}
}

View File

@@ -1,4 +1,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mogo.module.share" >
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<application>
<service android:name=".VoiceCmdService" />
<receiver android:name=".ShareVoiceCmdReceiver">
<intent-filter>
<action android:name="com.zhidao.speech.awake.notify" />
</intent-filter>
</receiver>
</application>
</manifest>

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,22 @@
package com.mogo.module.share
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.mogo.module.share.constant.ShareConstants
/**
* 用于接收唤醒词指令,现在只接收 com.zhidao.speech.awake.notify 这一条广播
*/
class ShareVoiceCmdReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// todo 接收唤醒词指令
val command = intent.getStringExtra("command")
if (command == "zhunbeishangbao") {
//todo 开启服务,准备上报求助
val seekHelp = Intent(context, VoiceCmdService::class.java)
seekHelp.putExtra(ShareConstants.VOICE_CMD_SERVICE_EVENT_KEY, ShareConstants.VOICE_CMD_SERVICE_SEEK_HELP)
context.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,false)
}
}
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,15 @@
package com.mogo.module.share.bean
import com.mogo.utils.network.utils.GsonUtil
/**
* 故障求助接口请求参数
*
* @param sn 当前用户sn
* @param vehicleType 改变成的目标车辆类型因为是故障求助所以默认是改成4故障车辆
*/
data class SeekRequest(val sn:String,val vehicleType:Int = 4)
fun SeekRequest.getJson():String{
return GsonUtil.jsonFromObject(this)
}

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,true);
}
}
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,193 @@
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.bean.SeekRequest
import com.mogo.module.share.bean.getJson
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.DeviceIdUtils
import com.mogo.utils.logger.Logger
import com.mogo.utils.network.RequestOptions
import com.mogo.utils.network.utils.GsonUtil
import com.zhidao.auto.platform.util.DeviceUtil
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的初始化放到了此方法后面如果有增加需要把这部分提出去
*
* @param useLocalVoiceNotice 使用自己的语音播报,如果是从语音助手过来的请求,语音助手可能会走自己的播报,默认是使用自己的播报
*/
fun seekHelp(context: Context, seekHelpListener: ISeekHelpListener,useLocalVoiceNotice:Boolean = true) {
// context初始化
if (this.context == null) {
this.context = context
aiAssist = AIAssist.getInstance(context)
}
isSeekHelp = ShareControl.getInstance(SeekHelpManager.context).mogoServiceApis.statusManagerApi.isSeekHelping
Logger.d(TAG, "开始故障求助上报---${isSeekHelp}")
seekListenerList.add(seekHelpListener)
when {
isSeekHelp -> {
// 正在求助中,进行异常提示
toast("已发布故障求助,请耐心等待")
aiAssist?.speakTTSVoice("已发布故障求助,请耐心等待")
}
getSeekAmountByLimitTime() >= SEEK_RECORD_LIMIT_AMOUNT -> {
// 超过限制时间内的限制次数,进行异常提示
toast("已在求助状态,请勿连续发布哦")
aiAssist?.speakTTSVoice("已在求助状态,请勿连续发布哦")
}
else -> {
// 没有异常情况,开始故障求助
if(useLocalVoiceNotice) {
// 语音说完再请求,要不然可能请求的太快
aiAssist?.speakTTSVoice(VOICE_WILL_SEEK_HELP, voiceCallback)
}else{
Logger.d(TAG,"不使用本地语音播报,直接开始发起求助")
realSeekHelp()
}
}
}
}
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")
// 请求故障求助接口
val seekRequest = SeekRequest(DeviceUtil.getSn())
val param = mutableMapOf("data" to seekRequest.getJson())
ShareControl.getInstance(context).mogoServiceApis.networkApi.create(ShareApiService::class.java, HttpConstant.getNetHost()).sendHelpSignal(param).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(object : SubscribeImpl<BaseData>(RequestOptions.create(context)) {
override fun onSuccess(o: BaseData?) {
super.onSuccess(o)
// 接口请求成功内部同步v2x状态通知adas改变自车图标
ShareControl.getInstance(context).mogoServiceApis.statusManagerApi.setSeekHelping("ShareDialog",true)
isSeekHelp = true
aiAssist?.speakTTSVoice("已发布求助信息,将为你通知其他车主")
toast("已发布求助信息,将为你通知其他车主")
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.*
/**
* 分享用到的api接口
*/
interface ShareApiService {
/**
* 发起求助接口
*/
@FormUrlEncoded
@POST("/yycp-realtimeLocations/vehicleTypeManage/car/updateVehicleType/v1")
fun sendHelpSignal(@FieldMap param: Map<String, String>): Observable<BaseData>
}

View File

@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#88000000">
<View
android:id="@+id/vBg"
android:layout_width="@dimen/share_module_width"
android:layout_height="@dimen/share_module_height"
android:layout_gravity="center"
android:background="@drawable/shape_bg_222533_20px"
android:clickable="true"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/btn_share_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/share_module_title_margin_top"
android:text="一 我要分享 一"
android:textColor="@color/white"
android:textSize="@dimen/share_module_title_content"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="@+id/vBg"
app:layout_constraintStart_toStartOf="@+id/vBg"
app:layout_constraintTop_toTopOf="@+id/vBg" />
<TextView
android:id="@+id/tvBlock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="@drawable/share_block_up"
android:drawablePadding="@dimen/share_module_tv_margin_top"
android:gravity="center"
android:text="上报拥堵"
android:textColor="@color/white"
android:textSize="@dimen/share_module_item"
android:textStyle="bold"
android:layout_marginTop="@dimen/dp_30"
app:layout_constraintLeft_toLeftOf="@+id/vBg"
app:layout_constraintRight_toLeftOf="@+id/tvTrafficCheck"
app:layout_constraintTop_toBottomOf="@+id/btn_share_title" />
<TextView
android:id="@+id/tvTrafficCheck"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="@drawable/share_traffic_check"
android:drawablePadding="@dimen/share_module_tv_margin_top"
android:gravity="center"
android:text="上报交通检查"
android:textColor="@color/white"
android:textSize="@dimen/share_module_item"
android:textStyle="bold"
app:layout_constraintLeft_toRightOf="@+id/tvBlock"
app:layout_constraintRight_toLeftOf="@+id/tvClosure"
app:layout_constraintRight_toRightOf="@+id/vBg"
app:layout_constraintTop_toTopOf="@+id/tvBlock" />
<TextView
android:id="@+id/tvClosure"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="@drawable/share_road_closure"
android:drawablePadding="@dimen/share_module_tv_margin_top"
android:gravity="center"
android:text="上报封路"
android:textColor="@color/white"
android:textSize="@dimen/share_module_item"
android:textStyle="bold"
app:layout_constraintLeft_toRightOf="@+id/tvTrafficCheck"
app:layout_constraintRight_toLeftOf="@+id/tvNeedHelp"
app:layout_constraintRight_toRightOf="@+id/vBg"
app:layout_constraintTop_toTopOf="@+id/tvTrafficCheck" />
<TextView
android:id="@+id/tvNeedHelp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="@drawable/share_oil_price"
android:drawablePadding="@dimen/share_module_tv_margin_top"
android:gravity="center"
android:text="故障求助"
android:textColor="@color/white"
android:textSize="@dimen/share_module_item"
android:textStyle="bold"
app:layout_constraintLeft_toRightOf="@+id/tvClosure"
app:layout_constraintRight_toRightOf="@+id/vBg"
app:layout_constraintTop_toTopOf="@+id/tvClosure" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="可以对小智说:上报拥堵、上报交通检查、上报封路"
android:textColor="@color/white_40"
android:textSize="@dimen/share_module_bottom_size"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/vBg"
app:layout_constraintEnd_toEndOf="@+id/vBg"
app:layout_constraintStart_toStartOf="@+id/vBg"
app:layout_constraintTop_toBottomOf="@+id/tvBlock" />
</androidx.constraintlayout.widget.ConstraintLayout>