Commit 5c8b03ca by 刘鹏

feat : mdt基础组件接入完毕

parent 67333c9a
...@@ -12,7 +12,8 @@ kapt { ...@@ -12,7 +12,8 @@ kapt {
} }
android { android {
compileSdkVersion 28 compileSdkVersion rootProject.ext.android["compileSdkVersion"]
buildToolsVersion rootProject.ext.android["buildToolsVersion"]
compileOptions { compileOptions {
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
...@@ -20,8 +21,8 @@ android { ...@@ -20,8 +21,8 @@ android {
defaultConfig { defaultConfig {
applicationId "com.ydl.component" applicationId "com.ydl.component"
minSdkVersion 21 minSdkVersion rootProject.ext.android["minSdkVersion"]
targetSdkVersion 28 targetSdkVersion rootProject.ext.android["targetSdkVersion"]
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
multiDexEnabled true multiDexEnabled true
...@@ -37,8 +38,13 @@ android { ...@@ -37,8 +38,13 @@ android {
multiDexEnabled true multiDexEnabled true
ndk { ndk {
abiFilters "arm64-v8a" // 指定要ndk需要兼容的架构(这样其他依赖包里mips,x86,armeabi,arm-v8之类的so会被过滤掉) abiFilters "arm64-v8a","armeabi-v7a" // 指定要ndk需要兼容的架构(这样其他依赖包里mips,x86,armeabi,arm-v8之类的so会被过滤掉)
} }
manifestPlaceholders = [
// TPNS 推送服务 accessId、accessKey
XG_ACCESS_ID : "",
XG_ACCESS_KEY: ""
]
} }
//配置签名文件 //配置签名文件
...@@ -175,16 +181,20 @@ android { ...@@ -175,16 +181,20 @@ android {
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.2.0' implementation(rootProject.ext.dependencies["appcompat-v7"])
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// kapt rootProject.ext.dependencies["dagger2-compiler"] // kapt rootProject.ext.dependencies["dagger2-compiler"]
api rootProject.ext.dependencies["butterknife"] api rootProject.ext.dependencies["butterknife"]
kapt rootProject.ext.dependencies["butterknife-compiler"] kapt rootProject.ext.dependencies["butterknife-compiler"]
implementation 'com.tencent.tpns:tpns:1.3.2.1-release' implementation(rootProject.ext.dependencies["design"])
implementation(rootProject.ext.dependencies["appcompat-v7"])
implementation(rootProject.ext.dependencies["espresso-core"])
implementation(rootProject.ext.dependencies["okhttp3"])
// TPNS SDK 主工程依赖包
implementation 'com.tencent.tpns:tpns:1.3.2.1-release'
if (true) { if (true) {
//开发模式 //开发模式
implementation fileTree(dir: 'aars', include: ['*.aar']) implementation fileTree(dir: 'aars', include: ['*.aar'])
...@@ -222,6 +232,7 @@ dependencies { ...@@ -222,6 +232,7 @@ dependencies {
implementation project(':m-fm') implementation project(':m-fm')
implementation modularPublication('com.ydl:m-fm-api') implementation modularPublication('com.ydl:m-fm-api')
implementation project(':m-mdt-tuicalling')
} else { } else {
//发布模式 //发布模式
......
...@@ -75,6 +75,43 @@ ...@@ -75,6 +75,43 @@
android:name=".home.HomeActivity" android:name=".home.HomeActivity"
android:launchMode="singleTask" android:launchMode="singleTask"
android:noHistory="true"></activity> android:noHistory="true"></activity>
<activity
android:name=".rtc.MDTLoginActivity"
android:launchMode="singleTask"
android:theme="@style/Theme.AppCompat.NoActionBar" />
<activity
android:name=".rtc.MDTMainActivity"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="com.tencent.liteav.action.portal" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="com.tencent.qcloud"
android:path="/detail"
android:scheme="pushscheme" />
</intent-filter>
<intent-filter>
<action android:name="com.tencent.trtc.tuicalling" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".rtc.ProfileActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.NoActionBar" />
<activity
android:name=".rtc.TUICallingEntranceActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.NoActionBar" />
</application> </application>
</manifest> </manifest>
\ No newline at end of file
...@@ -20,6 +20,7 @@ import com.ydl.component.music.FragmentContainerActivity ...@@ -20,6 +20,7 @@ import com.ydl.component.music.FragmentContainerActivity
import com.ydl.component.mvp.DemoContract import com.ydl.component.mvp.DemoContract
import com.ydl.component.mvp.DemoPresenter import com.ydl.component.mvp.DemoPresenter
import com.ydl.component.route.PlatformTempCommonRouteImpl import com.ydl.component.route.PlatformTempCommonRouteImpl
import com.ydl.component.rtc.MDTLoginActivity
import com.ydl.confide.home.ConfideHomeActivity import com.ydl.confide.home.ConfideHomeActivity
import com.ydl.media.audio.PlayService import com.ydl.media.audio.PlayService
import com.ydl.ydlcommon.modular.ModularServiceManager import com.ydl.ydlcommon.modular.ModularServiceManager
...@@ -83,6 +84,11 @@ class MainActivity : BaseLceActivity<DemoContract.View, DemoContract.Presenter>( ...@@ -83,6 +84,11 @@ class MainActivity : BaseLceActivity<DemoContract.View, DemoContract.Presenter>(
bindService() bindService()
reLoadData() reLoadData()
requestPermission() requestPermission()
bt_mdt.setOnClickListener {
val intent = Intent(this, MDTLoginActivity::class.java)
startActivity(intent)
}
tv_user.setOnClickListener { tv_user.setOnClickListener {
reLoadData() reLoadData()
} }
......
package com.ydl.component.rtc;
import android.content.Intent;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.blankj.utilcode.util.ToastUtils;
import com.tencent.imsdk.v2.V2TIMCallback;
import com.tencent.imsdk.v2.V2TIMManager;
import com.tencent.imsdk.v2.V2TIMSDKConfig;
import com.tencent.imsdk.v2.V2TIMSDKListener;
import com.tencent.imsdk.v2.V2TIMUserFullInfo;
import com.tencent.imsdk.v2.V2TIMValueCallback;
import com.tencent.liteav.basic.UserModel;
import com.tencent.liteav.basic.UserModelManager;
import com.tencent.qcloud.tuicore.TUILogin;
import com.ydl.component.R;
import com.ydl.component.rtc.debug.GenerateTestUserSig;
import java.util.ArrayList;
import java.util.List;
public class MDTLoginActivity extends AppCompatActivity {
private static final String TAG = "LoginActivity";
private EditText mEditUserId;
private Button mButtonLogin;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initStatusBar();
initView();
}
private void initView() {
mEditUserId = (EditText) findViewById(R.id.et_userId);
initButtonLogin();
}
private void initButtonLogin() {
mButtonLogin = (Button) findViewById(R.id.tv_login);
mButtonLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
login();
}
});
}
private void login() {
String userId = mEditUserId.getText().toString().trim();
if (TextUtils.isEmpty(userId)) {
Toast.makeText(this, R.string.user_id_is_empty, Toast.LENGTH_SHORT).show();
return;
}
final UserModelManager manager = UserModelManager.getInstance();
final UserModel userModel = manager.getUserModel();
userModel.phone = userId;
userModel.userId = userId;
userModel.userSig = GenerateTestUserSig.genTestUserSig(userId);
manager.setUserModel(userModel);
V2TIMSDKConfig config = new V2TIMSDKConfig();
config.setLogLevel(V2TIMSDKConfig.V2TIM_LOG_DEBUG);
TUILogin.init(this, GenerateTestUserSig.SDKAPPID, null, new V2TIMSDKListener() {
@Override
public void onKickedOffline() {
}
@Override
public void onUserSigExpired() {
}
});
TUILogin.login(userModel.userId, userModel.userSig, new V2TIMCallback() {
@Override
public void onError(int code, String msg) {
ToastUtils.showLong("登录IM失败,所有功能不可用[%d]%s", code, msg);
Log.d(TAG, "login fail code: " + code + " msg:" + msg);
}
@Override
public void onSuccess() {
Log.d(TAG, "login onSuccess");
getUserInfo();
}
});
}
private void getUserInfo() {
final UserModelManager manager = UserModelManager.getInstance();
final UserModel userModel = manager.getUserModel();
//先查询用户是否存在
List<String> userIdList = new ArrayList<>();
userIdList.add(userModel.userId);
Log.d(TAG, "setUserInfo: userIdList = " + userIdList);
V2TIMManager.getInstance().getUsersInfo(userIdList, new V2TIMValueCallback<List<V2TIMUserFullInfo>>() {
@Override
public void onError(int code, String msg) {
Log.e(TAG, "get group info list fail, code:" + code + " msg: " + msg);
}
@Override
public void onSuccess(List<V2TIMUserFullInfo> resultList) {
if (resultList == null || resultList.isEmpty()) {
return;
}
V2TIMUserFullInfo result = resultList.get(0);
String userName = result.getNickName();
String userAvatar = result.getFaceUrl();
Log.d(TAG, "onSuccess: userName = " + userName + " , userAvatar = " + userAvatar);
//如果用户名和头像为空,则跳转设置界面进行设置
if (TextUtils.isEmpty(userName) || TextUtils.isEmpty(userAvatar)) {
Intent intent = new Intent(MDTLoginActivity.this, ProfileActivity.class);
startActivity(intent);
finish();
} else {
userModel.userAvatar = userAvatar;
userModel.userName = userName;
manager.setUserModel(userModel);
//如果用户信息不为空,则直接进入主界面
Intent intent = new Intent(MDTLoginActivity.this, MDTMainActivity.class);
startActivity(intent);
finish();
}
}
});
}
private void initStatusBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
}
}
package com.ydl.component.rtc;
import android.content.Intent;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.blankj.utilcode.util.ToastUtils;
import com.tencent.imsdk.v2.V2TIMCallback;
import com.tencent.imsdk.v2.V2TIMManager;
import com.tencent.imsdk.v2.V2TIMUserFullInfo;
import com.tencent.liteav.basic.AvatarConstant;
import com.tencent.liteav.basic.ImageLoader;
import com.tencent.liteav.basic.UserModel;
import com.tencent.liteav.basic.UserModelManager;
import com.ydl.component.R;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ProfileActivity extends AppCompatActivity {
private static final String TAG = "ProfileActivity";
private ImageView mImageAvatar;
private EditText mEditUserName;
private Button mButtonRegister;
private TextView mTvInputTips; //昵称限制提示
private String mAvatarUrl; //用户头像
//自定义随机登录名
private static final int[] CUSTOM_NAME_ARRAY = {
R.string.app_custom_name_1,
R.string.app_custom_name_2,
R.string.app_custom_name_3,
R.string.app_custom_name_4,
R.string.app_custom_name_5,
R.string.app_custom_name_6,
R.string.app_custom_name_7,
R.string.app_custom_name_8,
R.string.app_custom_name_9,
R.string.app_custom_name_10,
R.string.app_custom_name_11,
R.string.app_custom_name_12,
};
private void startMainActivity() {
Intent intent = new Intent();
intent.addCategory("android.intent.category.DEFAULT");
intent.setAction("com.tencent.liteav.action.portal");
startActivity(intent);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login_profile);
initStatusBar();
initView();
}
private void initView() {
mImageAvatar = (ImageView) findViewById(R.id.iv_user_avatar);
mEditUserName = (EditText) findViewById(R.id.et_user_name);
mButtonRegister = (Button) findViewById(R.id.tv_register);
mTvInputTips = (TextView) findViewById(R.id.tv_tips_user_name);
String[] avatarArr = AvatarConstant.USER_AVATAR_ARRAY;
int index = new Random().nextInt(avatarArr.length);
mAvatarUrl = avatarArr[index];
ImageLoader.loadImage(this, mImageAvatar, mAvatarUrl, R.mipmap.ic_avatar);
mButtonRegister.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setProfile();
}
});
int customNameIndex = new Random().nextInt(CUSTOM_NAME_ARRAY.length);
mEditUserName.setText(getString(CUSTOM_NAME_ARRAY[customNameIndex]));
String text = mEditUserName.getText().toString();
if (!TextUtils.isEmpty(text)) {
mEditUserName.setSelection(text.length());
}
mEditUserName.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence text, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence text, int start, int before, int count) {
mButtonRegister.setEnabled(text.length() != 0);
String editable = mEditUserName.getText().toString();
//匹配字母,数字,中文,下划线,以及限制输入长度为2-20.
Pattern p = Pattern.compile("^[a-z0-9A-Z\\u4e00-\\u9fa5\\_]{2,20}$");
Matcher m = p.matcher(editable);
if (!m.matches()) {
mTvInputTips.setTextColor(getResources().getColor(R.color.color_input_no_match));
} else {
mTvInputTips.setTextColor(getResources().getColor(R.color.text_color_hint));
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
private void setProfile() {
final String userName = mEditUserName.getText().toString().trim();
if (TextUtils.isEmpty(userName)) {
ToastUtils.showLong(getString(R.string.app_hint_user_name));
return;
}
String reg = "^[a-z0-9A-Z\\u4e00-\\u9fa5\\_]{2,20}$";
if (!userName.matches(reg)) {
mTvInputTips.setTextColor(getResources().getColor(R.color.color_input_no_match));
return;
}
mTvInputTips.setTextColor(getResources().getColor(R.color.text_color_hint));
V2TIMUserFullInfo v2TIMUserFullInfo = new V2TIMUserFullInfo();
v2TIMUserFullInfo.setFaceUrl(mAvatarUrl);
v2TIMUserFullInfo.setNickname(userName);
V2TIMManager.getInstance().setSelfInfo(v2TIMUserFullInfo, new V2TIMCallback() {
@Override
public void onError(int code, String desc) {
Log.e(TAG, "set profile failed errorCode : " + code + " errorMsg : " + desc);
//头像和昵称设置失败,也可以进入到主界面(头像和昵称都用默认值),不影响功能
ToastUtils.showLong(getString(R.string.app_toast_failed_to_set, desc));
startMainActivity();
finish();
}
@Override
public void onSuccess() {
Log.i(TAG, "set profile success.");
ToastUtils.showLong(getString(R.string.app_toast_register_success_and_logging_in));
//成功后保存用户数据
UserModel userModel = UserModelManager.getInstance().getUserModel();
userModel.userName = userName;
userModel.userAvatar = mAvatarUrl;
UserModelManager.getInstance().setUserModel(userModel);
startMainActivity();
finish();
}
});
}
private void initStatusBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/color_blue" />
<corners android:radius="20dp" />
</shape>
...@@ -2,5 +2,5 @@ ...@@ -2,5 +2,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"> android:shape="rectangle">
<solid android:color="#F4F5F9" /> <solid android:color="#F4F5F9" />
<corners android:radius="10dp"/> <corners android:radius="26dp" />
</shape> </shape>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/color_white" />
<corners android:radius="10dp" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FF999999" />
<corners android:radius="15dp" />
</shape>
\ No newline at end of file
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_white"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
android:layout_marginTop="28dp"
android:layout_marginEnd="10dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="@drawable/ic_back">
<TextView
android:id="@+id/toolbar_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:singleLine="true"
android:text="@string/app_name"
android:textColor="@color/color_black"
android:textSize="18sp"
android:textStyle="bold" />
<Button
android:id="@+id/btn_ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|center_vertical"
android:background="@android:color/transparent"
android:text="@string/btn_confirm"
android:textColor="@color/font_blue"
android:visibility="gone" />
<ImageButton
android:id="@+id/btn_link"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="right|center_vertical"
android:background="@color/color_transparent"
android:padding="10dp"
android:src="@drawable/ic_question_link" />
</androidx.appcompat.widget.Toolbar>
<LinearLayout
android:id="@+id/ll_list"
android:layout_width="match_parent"
android:layout_height="200dp"
android:orientation="vertical"
android:padding="20dp"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_added_member"
android:textColor="@color/black"
android:textSize="18sp"
android:textStyle="bold" />
<ListView
android:id="@+id/list_member"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_edit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="10dp"
android:background="@drawable/bg_edit_text"
android:padding="8dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/tv_search"
app:layout_constraintTop_toBottomOf="@+id/ll_list">
<TextView
android:id="@+id/tv_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableEnd="@drawable/arrow_right"
android:includeFontPadding="false"
android:text="ID"
android:textColor="@color/color_black"
app:layout_constraintBaseline_toBaselineOf="@id/et_search_user"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/et_search_user"
android:layout_width="210dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:background="@null"
android:hint="@string/search_call_hint"
android:imeOptions="actionSearch"
android:inputType="number"
android:maxLength="11"
android:maxLines="1"
android:singleLine="true"
android:textColor="@color/color_black"
android:textColorHint="@color/text_color_hint"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@id/cl_edit"
app:layout_constraintLeft_toRightOf="@id/tv_id" />
<ImageView
android:id="@+id/iv_clear_search"
android:layout_width="22dp"
android:layout_height="22dp"
android:background="@drawable/ic_clear_search"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/cl_edit"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/cl_edit" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/tv_search"
android:layout_width="76dp"
android:layout_height="38dp"
android:layout_marginEnd="20dp"
android:background="@drawable/bg_btn_search"
android:gravity="center"
android:text="@string/trtccalling_search"
android:textColor="@color/color_white"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@id/cl_edit"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/cl_edit" />
<TextView
android:id="@+id/line_seperate"
android:layout_width="3dp"
android:layout_height="12dp"
android:layout_marginTop="21dp"
android:layout_marginEnd="22dp"
android:background="@drawable/bg_line_radius"
app:layout_constraintLeft_toLeftOf="@id/cl_edit"
app:layout_constraintTop_toBottomOf="@id/cl_edit" />
<TextView
android:id="@+id/tv_self_userid"
android:layout_width="wrap_content"
android:layout_height="22dp"
android:layout_marginStart="8dp"
android:layout_marginTop="17dp"
android:text="@string/self_userId"
android:textColor="#333333"
android:textSize="14sp"
app:layout_constraintStart_toEndOf="@id/line_seperate"
app:layout_constraintTop_toBottomOf="@id/cl_edit" />
<LinearLayout
android:id="@+id/ll_contract"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:gravity="center_vertical"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="@id/tv_search"
app:layout_constraintStart_toStartOf="@+id/cl_edit"
app:layout_constraintTop_toBottomOf="@+id/tv_self_userid"
tools:visibility="visible">
<com.tencent.liteav.trtccalling.ui.common.RoundCornerImageView
android:id="@+id/img_avatar"
android:layout_width="44dp"
android:layout_height="44dp"
android:src="@drawable/ic_avatar"
app:trtc_radius="15dp" />
<TextView
android:id="@+id/tv_user_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_weight="1"
android:textColor="@color/color_black"
android:textSize="16sp" />
<Button
android:id="@+id/btn_start_call"
android:layout_width="56dp"
android:layout_height="26dp"
android:background="@drawable/bg_btn_search"
android:gravity="center"
android:text="@string/trtccalling_start_call"
android:textAllCaps="false"
android:textColor="@color/color_white" />
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_tips"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="120dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cl_edit">
<ImageView
android:id="@+id/iv_contacts_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_contacts_search"
app:layout_constraintBottom_toBottomOf="@id/cl_tips"
app:layout_constraintLeft_toLeftOf="@id/cl_tips"
app:layout_constraintRight_toRightOf="@id/cl_tips"
app:layout_constraintTop_toTopOf="@id/cl_tips" />
<TextView
android:id="@+id/tv_contacts_tips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="11dp"
android:layout_marginTop="10dp"
android:text="@string/tips_search"
android:textColor="@color/color_input_normal"
android:textSize="14sp"
app:layout_constraintLeft_toLeftOf="@id/cl_tips"
app:layout_constraintRight_toRightOf="@id/cl_tips"
app:layout_constraintTop_toBottomOf="@id/iv_contacts_search" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="16dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/item_cl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_item_main_list">
<ImageView
android:id="@+id/img_item"
android:layout_width="170dp"
android:layout_height="136dp"
android:layout_marginStart="14dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_audio_call" />
<TextView
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="34dp"
android:layout_marginEnd="10dp"
android:textColor="@color/color_black"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintLeft_toRightOf="@id/img_item"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="10dp"
android:paddingBottom="20dp"
android:textColor="#666666"
android:textSize="14sp"
app:layout_constraintLeft_toRightOf="@id/img_item"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_title" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:id="@+id/bottom_line"
android:layout_width="match_parent"
android:layout_height="25dp"
android:visibility="gone" />
</LinearLayout>
<?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="@color/color_white">
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="120dp"
android:text="@string/welcome_tips"
android:textColor="@color/color_black"
android:textSize="32sp"
app:layout_constraintBottom_toTopOf="@id/cl_edit"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="30dp"
android:background="@drawable/bg_edit_text"
android:padding="16dp"
app:layout_constraintBottom_toTopOf="@id/tv_login"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<TextView
android:id="@+id/tv_phone_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/user_id"
android:textColor="@color/color_black"
android:textSize="16sp"
app:layout_constraintLeft_toLeftOf="@id/cl_edit"
app:layout_constraintTop_toTopOf="@id/cl_edit" />
<EditText
android:id="@+id/et_userId"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="30dp"
android:layout_marginRight="30dp"
android:background="@null"
android:hint="@string/hint_user_id"
android:inputType="number"
android:maxLength="11"
android:maxLines="1"
android:singleLine="true"
android:textColor="@color/color_black"
android:textColorHint="@color/color_input_normal"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@id/tv_phone_number"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintLeft_toRightOf="@id/tv_phone_number"
app:layout_constraintStart_toEndOf="@+id/tv_phone_number" />
</androidx.constraintlayout.widget.ConstraintLayout>
<Button
android:id="@+id/tv_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:background="@drawable/btn_border"
android:text="@string/btn_login"
android:textColor="@color/color_white"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<?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="@color/color_white">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:text="@string/app_title_register"
android:textColor="@color/color_black"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/iv_user_avatar"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="30dp"
android:src="@mipmap/ic_avatar"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_title" />
<EditText
android:id="@+id/et_user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="30dp"
android:layout_marginRight="20dp"
android:background="@drawable/bg_edit_text"
android:hint="@string/app_hint_user_name"
android:maxLines="1"
android:padding="20dp"
android:singleLine="true"
android:textColor="@color/color_black"
android:textColorHint="#BBBBBB"
android:textSize="16sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_user_avatar" />
<TextView
android:id="@+id/tv_tips_user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="20dp"
android:text="@string/app_tips_user_name"
android:textColor="@color/text_color_hint"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/et_user_name" />
<Button
android:id="@+id/tv_register"
android:layout_width="match_parent"
android:layout_height="52dp"
android:layout_marginLeft="20dp"
android:layout_marginTop="30dp"
android:layout_marginRight="20dp"
android:background="@drawable/btn_border"
android:text="@string/app_title_register"
android:textColor="@color/color_white"
android:textSize="16sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_tips_user_name" />
</androidx.constraintlayout.widget.ConstraintLayout>
...@@ -218,6 +218,14 @@ ...@@ -218,6 +218,14 @@
android:layout_marginRight="10dp" android:layout_marginRight="10dp"
android:text="我的" /> android:text="我的" />
<Button
android:id="@+id/bt_mdt"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginRight="10dp"
android:text="mdt" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
......
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_white">
<RelativeLayout
android:id="@+id/rl_title"
android:layout_width="match_parent"
android:layout_height="42dp"
android:layout_marginTop="28dp"
android:background="@color/color_white">
<TextView
android:id="@+id/main_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/app_name"
android:textColor="@color/color_black"
android:textSize="18sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/img_logout"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerVertical="true"
android:layout_marginStart="15dp"
android:src="@mipmap/ic_avatar"
android:visibility="gone" />
<TextView
android:id="@+id/tv_login_out"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerInParent="true"
android:layout_centerVertical="true"
android:layout_marginRight="20dp"
android:text="@string/app_login_out"
android:textColor="@color/color_black"
android:textSize="16sp" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/rl_title"
android:background="@color/color_main_bg"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/main_recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_weight="1" />
</LinearLayout>
</RelativeLayout>
<?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="270dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/bg_item_main_list">
<TextView
android:id="@+id/tv_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:text="aa"
android:textColor="@color/color_main_text"
android:textSize="16sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/view_divide"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="20dp"
android:background="@color/color_line"
app:layout_constraintTop_toBottomOf="@id/tv_message" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_button_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/view_divide">
<Button
android:id="@+id/btn_negative"
android:layout_width="match_parent"
android:layout_height="44dp"
android:background="@android:color/transparent"
android:gravity="center"
android:text="@string/btn_confirm"
android:textColor="@color/color_blue"
android:textSize="17sp"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:gravity="center_vertical"
android:padding="5dp">
<com.tencent.liteav.trtccalling.ui.common.RoundCornerImageView
android:id="@+id/img_avatar"
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/ic_avatar"
app:trtc_radius="12dp" />
<TextView
android:id="@+id/tv_user_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_weight="1"
android:textColor="@color/color_black"
android:textSize="16sp" />
<Button
android:id="@+id/btn_remove"
android:layout_width="56dp"
android:layout_height="26dp"
android:background="@drawable/bg_btn_search"
android:gravity="center"
android:text="@string/app_remove"
android:textAllCaps="false"
android:textColor="@color/color_white" />
</LinearLayout>
\ No newline at end of file
<?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="270dp"
android:layout_height="142dp"
android:layout_gravity="center"
android:background="@drawable/bg_edit_text">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:text="@string/dialog_log_out"
android:textColor="@color/color_black"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/view_divide"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="43dp"
android:background="@color/text_color_hint"
app:layout_constraintBottom_toBottomOf="parent" />
<View
android:layout_width="1dp"
android:layout_height="43dp"
android:background="@color/text_color_hint"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_button_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent">
<Button
android:id="@+id/btn_negative"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:text="@string/btn_cancel"
android:textColor="@color/color_blue"
android:textSize="17sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@id/cl_button_panel"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintLeft_toLeftOf="@id/cl_button_panel"
app:layout_constraintRight_toLeftOf="@+id/btn_positive" />
<Button
android:id="@+id/btn_positive"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:text="@string/btn_confirm"
android:textColor="@color/color_blue"
android:textSize="17sp"
app:layout_constraintBottom_toBottomOf="@id/cl_button_panel"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintLeft_toRightOf="@id/btn_negative"
app:layout_constraintRight_toRightOf="@id/cl_button_panel" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingLeft="16dp"
android:paddingRight="@dimen/activity_horizontal_margin"> android:paddingRight="16dp">
<TextView <TextView
android:id="@+id/tv_current_time" android:id="@+id/tv_current_time"
......
...@@ -12,7 +12,17 @@ ...@@ -12,7 +12,17 @@
<color name="blue">#2196F3</color> <color name="blue">#2196F3</color>
<color name="grey">#9E9E9E</color> <color name="grey">#9E9E9E</color>
<color name="color_white">#ffffff</color>
<color name="color_black">#000000</color>
<color name="color_transparent">#00000000</color>
<color name="text_color_hint">#BBBBBB</color>
<color name="color_blue">#006EFF</color>
<color name="color_bg_dialog">#66000000</color>
<color name="color_line">#DDDDDD</color>
<color name="color_input_normal">#BBBBBB</color>
<color name="color_input_no_match">#FA585E</color>
<color name="color_main_bg">#F1F3F8</color>
<color name="color_main_text">#333333</color>
<!-- ============ 标准颜色 ============ --> <!-- ============ 标准颜色 ============ -->
<!--主题色 - 用于TabBar、突出文字、按钮和icon--> <!--主题色 - 用于TabBar、突出文字、按钮和icon-->
......
...@@ -4,4 +4,65 @@ ...@@ -4,4 +4,65 @@
<string name="mode_loop">列表循环</string> <string name="mode_loop">列表循环</string>
<string name="mode_shuffle">随机播放</string> <string name="mode_shuffle">随机播放</string>
<string name="mode_one">单曲循环</string> <string name="mode_one">单曲循环</string>
<string name="app_toast_login_success">登录成功</string>
<string name="app_toast_login_fail">登录IM失败,所有功能不可用[%d]%s</string>
<string name="working">正在运行中</string>
<string name="self_userId">您的userId:</string>
<string name="tips_search">搜索添加已注册用户\n\t\t\t以发起通话</string>
<string name="toast_video_call">视频呼叫:%s</string>
<string name="toast_voice_call">语音呼叫:%s</string>
<string name="toast_not_call_myself">不能呼叫自己</string>
<string name="call_self_format">您的userId: %s</string>
<string name="audio_call">语音通话</string>
<string name="video_call">视频通话</string>
<string name="btn_cancel">取消</string>
<string name="btn_confirm">确定</string>
<string name="search_call_hint">搜索 userId</string>
<string name="user_id">用户名</string>
<string name="hint_user_id">请输入登录的userId</string>
<string name="btn_login">登录</string>
<string name="welcome_tips">欢迎使用\n腾讯云实时音视频</string>
<string name="dialog_log_out">确定要退出登录吗?</string>
<string name="user_sig_expired">您的用户签名信息已过期,请重新登录</string>
<string name="user_id_is_empty">userId 不能为空</string>
<string name="app_title_register">注册</string>
<string name="app_text_user_name">用户名</string>
<string name="app_hint_user_name">请输入用户名</string>
<string name="app_tips_user_name">仅限中文、字母、数字和下划线,2–20个字</string>
<string name="app_toast_register_success_and_logging_in">注册成功</string>
<string name="app_toast_failed_to_set">设置失败: %s</string>
<string name="app_item_multi_audio_call">多人语音通话</string>
<string name="app_item_multi_video_call">多人视频通话</string>
<string name="app_added_member">已添加成员</string>
<string name="app_toast_no_member">无通话成员</string>
<string name="app_toast_multi_call_num_exceed">添加失败,人数超出限制</string>
<string name="app_toast_user_added">用户已添加</string>
<string name="app_tips_search">搜索添加已注册用户\n\t\t\t以发起通话</string>
<string name="app_self_userid">您的ID:</string>
<string name="app_search">搜索</string>
<string name="app_start_call">呼叫</string>
<string name="app_user_not_exist">用户不存在</string>
<string name="app_add">添加</string>
<string name="app_remove">移除</string>
<string name="app_tv_voice_call_tips">丢包率>70%可正常语音通话</string>
<string name="app_tv_video_call_tips">丢包率>50%可正常视频通话</string>
<string name="app_login_out">退出登录</string>
<string name="app_permission_hint">为了应用在后台时响应通话请求,请到设置中打开应用“后台拉起界面”权限</string>
<!--自定义随机登录名-->
<string name="app_custom_name_1">路飞</string>
<string name="app_custom_name_2">山治</string>
<string name="app_custom_name_3">娜美</string>
<string name="app_custom_name_4">乌索普</string>
<string name="app_custom_name_5">香克斯</string>
<string name="app_custom_name_6">弗兰奇</string>
<string name="app_custom_name_7">罗宾</string>
<string name="app_custom_name_8">钢铁侠</string>
<string name="app_custom_name_9">蜘蛛侠</string>
<string name="app_custom_name_10">乔巴</string>
<string name="app_custom_name_11">鸣人</string>
<string name="app_custom_name_12">艾斯</string>
</resources> </resources>
<resources> <resources>
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="logoutDialogStyle" parent="@android:style/Theme.Holo.Dialog">
<item name="android:windowIsFloating">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowCloseOnTouchOutside">true</item>
<item name="android:windowBackground">@color/color_bg_dialog</item>
<item name="android:windowSoftInputMode">adjustResize</item>
</style>
</resources> </resources>
{
"tpns": {
"access_id": "",
"access_key": ""
},
"com.tencent.trtc": {
"channel": {
"enable": true,
"xiaomi": {
"app_id": "",
"app_key": ""
},
"vivo": {
"app_id": "",
"app_key": ""
},
"oppo": {
"app_id": "",
"app_key": ""
},
"meizu": {
"app_id": "",
"app_key": ""
}
}
},
"debug": true,
"version": "1.3.2.1-release",
"upgrade": false
}
\ No newline at end of file
apply plugin: 'com.android.library'
android {
compileSdkVersion rootProject.ext.android["compileSdkVersion"]
buildToolsVersion rootProject.ext.android["buildToolsVersion"]
defaultConfig {
minSdkVersion rootProject.ext.android["minSdkVersion"]
targetSdkVersion rootProject.ext.android["targetSdkVersion"]
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
api fileTree(dir: 'libs', include: ['*.jar'])
implementation(rootProject.ext.dependencies["support-multidex"])
implementation(rootProject.ext.dependencies["recyclerview-v7"])
implementation(rootProject.ext.dependencies["appcompat-v7"])
implementation(rootProject.ext.dependencies["design"])
implementation(rootProject.ext.dependencies["constraint-layout"])
implementation(rootProject.ext.dependencies["appcompat-v7"])
implementation(rootProject.ext.dependencies["constraint-layout"])
implementation(rootProject.ext.dependencies["okhttp3-logging"])
implementation(rootProject.ext.dependencies["retrofit-converter-gson"])
implementation(rootProject.ext.dependencies["retrofit"])
implementation(rootProject.ext.dependencies["okhttp3"])
implementation(rootProject.ext.dependencies["glide"])
implementation(rootProject.ext.dependencies["gson"])
implementation "de.hdodenhof:circleimageview:3.1.0"
implementation "com.blankj:utilcode:1.25.9"
implementation "com.tencent.liteav:LiteAVSDK_TRTC:latest.release"
api project(':m-mdt-tuicore')
}
...@@ -19,6 +19,3 @@ ...@@ -19,6 +19,3 @@
# If you keep the line number information, uncomment this to # If you keep the line number information, uncomment this to
# hide the original source file name. # hide the original source file name.
#-renamesourcefileattribute SourceFile #-renamesourcefileattribute SourceFile
-keep class com.tencent.** { *; }
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tencent.liteav.trtccalling">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<application>
<activity
android:name=".ui.base.BaseCallActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.NoActionBar" />
<provider
android:name=".model.impl.ServiceInitializer"
android:authorities="${applicationId}.ServiceInitializer"
android:enabled="true"
android:exported="false" />
<!-- 保活Service-->
<service
android:name=".ui.service.TUICallService"
android:enabled="true"
android:exported="false" />
<service
android:name=".ui.floatwindow.FloatWindowService"
android:enabled="true"
android:exported="false" />
</application>
</manifest>
\ No newline at end of file
package com.tencent.liteav.basic;
public interface AvatarConstant {
String USER_AVATAR_ARRAY [] = {
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar1.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar2.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar3.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar4.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar5.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar6.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar7.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar8.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar9.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar10.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar11.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar12.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar13.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar14.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar15.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar16.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar17.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar18.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar19.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar20.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar21.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar22.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar23.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar24.png",
};
}
package com.tencent.liteav.basic;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.widget.ImageView;
import androidx.annotation.DrawableRes;
import androidx.annotation.RawRes;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
import com.bumptech.glide.request.RequestOptions;
import java.security.MessageDigest;
import java.util.concurrent.ExecutionException;
public class ImageLoader {
private static int radius = 15; //TRTC默认图片圆角为15dp
public static void clear(Context context, ImageView imageView) {
Glide.with(context).clear(imageView);
}
public static void clear(Context context) {
Glide.with(context).pauseRequests();
}
public static void loadImage(Context context, ImageView imageView, String url) {
loadImage(context, imageView, url, 0, radius);
}
public static void loadImage(Context context, ImageView imageView, String url, @DrawableRes int errorResId) {
loadImage(context, imageView, url, errorResId, radius);
}
public static void loadImage(Context context, ImageView imageView, String url, @DrawableRes int errorResId, int radius) {
if (TextUtils.isEmpty(url)) {
if (imageView != null && errorResId != 0) {
imageView.setImageResource(errorResId);
}
return;
}
Glide.with(context).load(url).error(loadTransform(context, errorResId, radius)).into(imageView);
}
public static void loadImage(Context context, ImageView imageView, @RawRes @DrawableRes Integer resourceId) {
Glide.with(context).load(resourceId).into(imageView);
}
public static Bitmap getImage(Context context, String url, int width, int height) {
if (TextUtils.isEmpty(url)) {
return null;
}
try {
Bitmap bitmap = Glide.with(context)
.asBitmap()
.load(url)
.into(width, height)
.get();
return bitmap;
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
public static void loadImageThumbnail(Context context, ImageView imageView, String url, @DrawableRes int resourceId, int radius) {
Glide.with(context).load(url)
.apply(new RequestOptions().placeholder(resourceId).error(resourceId).centerCrop()
.transform(new GlideRoundTransform(imageView.getContext(), radius)))
.thumbnail(loadTransform(imageView.getContext(), resourceId, radius))
.into(imageView);
}
public static void loadGifImage(Context context, ImageView imageView, String url) {
loadGifImage(context, imageView, url, 0);
}
public static void loadGifImage(Context context, ImageView imageView, String url, @DrawableRes int errorResId) {
if (TextUtils.isEmpty(url)) {
if (imageView != null && errorResId != 0) {
imageView.setImageResource(errorResId);
}
return;
}
Glide.with(context).asGif().load(url).into(imageView);
}
public static void loadGifImage(Context context, ImageView imageView, @RawRes @DrawableRes int resourceId) {
Glide.with(context).asGif().load(resourceId).into(imageView);
}
private static RequestBuilder<Drawable> loadTransform(Context context, @DrawableRes int placeholderId, int radius) {
return Glide.with(context).load(placeholderId)
.apply(new RequestOptions().centerCrop().transform(new GlideRoundTransform(context, radius)));
}
public static class GlideRoundTransform extends BitmapTransformation {
private static float radius = 0f;
public GlideRoundTransform(Context context, int dp) {
radius = Resources.getSystem().getDisplayMetrics().density * dp;
}
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
return roundCrop(pool, toTransform);
}
private static Bitmap roundCrop(BitmapPool pool, Bitmap source) {
if (source == null) return null;
Bitmap result = pool.get(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);
if (result == null) {
result = Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);
}
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
paint.setShader(new BitmapShader(source, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
paint.setAntiAlias(true);
RectF rectF = new RectF(0f, 0f, source.getWidth(), source.getHeight());
canvas.drawRoundRect(rectF, radius, radius, paint);
return result;
}
@Override
public void updateDiskCacheKey(MessageDigest messageDigest) {
}
}
}
package com.tencent.liteav.basic;
import android.app.Application;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.util.Log;
public class IntentUtils {
private static final String TAG = "IntentUtils";
/**
* 确保存在相应的 activity 来处理 intent,以免发生 activity 找不到的异常。
*/
public static void safeStartActivity(Context context, Intent intent) {
if (intent == null || context == null) {
Log.e(TAG, "intent or activity is null");
return;
}
if (context.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) == null) {
Log.w(TAG, "No activity match : " + intent.toString());
return;
}
try {
if (context instanceof Application) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
Log.e("TAG", "ActivityNotFoundException : " + intent.toString());
}
}
}
package com.tencent.liteav.basic;
import java.io.Serializable;
public class UserModel implements Serializable {
public String phone;
public String userId;
public String userSig;
public String userName;
public String userAvatar;
public UserType userType = UserType.NONE;
public enum UserType {
NONE,
ROOM, // 多人音视频房间
CALLING, // 语音/视频通话
CHAT_SALON, // 语音沙龙
VOICE_ROOM, // 语音聊天室
LIVE_ROOM, // 视频互动直播
CHORUS, // 合唱
KARAOKE // 卡拉OK
}
}
package com.tencent.liteav.basic;
import android.util.Log;
import com.blankj.utilcode.util.GsonUtils;
import com.blankj.utilcode.util.SPUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
public class UserModelManager {
private static final String TAG = "UserModelManager";
private static final String PER_DATA = "per_profile_manager";
private static final String PER_USER_MODEL = "per_user_model";
private static final String PER_USER_DATE = "per_user_publish_video_date";
private static UserModelManager sInstance;
private UserModel mUserModel;
private String mUserPubishVideoDate;
public static UserModelManager getInstance() {
if (sInstance == null) {
synchronized (UserModelManager.class) {
if (sInstance == null) {
sInstance = new UserModelManager();
}
}
}
return sInstance;
}
public synchronized UserModel getUserModel() {
if (mUserModel == null) {
loadUserModel();
}
return mUserModel == null ? new UserModel() : mUserModel;
}
public synchronized void setUserModel(UserModel model) {
mUserModel = model;
try {
SPUtils.getInstance(PER_DATA).put(PER_USER_MODEL, GsonUtils.toJson(mUserModel));
} catch (Exception e) {
Log.d(TAG, "");
}
}
private void loadUserModel() {
try {
String json = SPUtils.getInstance(PER_DATA).getString(PER_USER_MODEL);
mUserModel = GsonUtils.fromJson(json, UserModel.class);
} catch (Exception e) {
}
}
private String getUserPublishVideoDate() {
if (mUserPubishVideoDate == null) {
mUserPubishVideoDate = SPUtils.getInstance(PER_DATA).getString(PER_USER_DATE, "");
}
return mUserPubishVideoDate;
}
private void setUserPublishVideoDate(String date) {
mUserPubishVideoDate = date;
try {
SPUtils.getInstance(PER_DATA).put(PER_USER_DATE, mUserPubishVideoDate);
} catch (Exception e) {
}
}
// 首次TRTC打开摄像头提示"Demo特别配置了无限期云端存储"
public boolean needShowSecurityTips() {
String profileDate = getUserPublishVideoDate();
Date date = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("dd");
String day = formatter.format(date);
if (!day.equals(profileDate)) {
setUserPublishVideoDate(day);
return true;
}
return false;
}
}
\ No newline at end of file
package com.tencent.liteav.trtccalling;
import android.view.View;
public interface TUICalling {
/* 呼叫类型 */
enum Type {
AUDIO,
VIDEO
}
/* 角色 */
enum Role {
CALL,
CALLED
}
enum Event {
CALL_START, // 通话开始
CALL_SUCCEED, // 通话接通成功
CALL_END, // 通话结束
CALL_FAILED, // 通话失败
}
/**
* 设置用户昵称
*
* @param nickname 用户昵称 (长度不得超过500个字节)
* @param callback 设置结果回调
*/
void setUserNickname(String nickname, TUICallingCallback callback);
/**
* 设置用户头像
*
* @param avatar 用户头像, 头像必须是URL格式 (长度不得超过500个字节)
* 例如: https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar1.png
* @param callback 设置结果回调
*/
void setUserAvatar(String avatar, TUICallingCallback callback);
/**
* 拨打电话
*
* @param userIDs 接听方用户userId数组
* @param type 呼叫类型
*/
void call(String[] userIDs, Type type);
/**
* 设置监听器
*
* @param listener 监听器
*/
void setCallingListener(TUICallingListener listener);
/**
* 设置铃声(建议在30s以内)
*
* @param filePath 接听方铃音路径
*/
void setCallingBell(String filePath);
/**
* 开启静音模式(接听方不响铃音)
*
* @param enable
*/
void enableMuteMode(boolean enable);
/**
* 开启悬浮窗
*
* @param enable
*/
void enableFloatWindow(boolean enable);
/**
* 开启自定义视图
* 开启后,会在呼叫/被叫开始回调中,接收到CallingView的实例,由开发者自行决定展示方式
* 注意:必须全屏或者与屏幕等比例展示,否则会有展示异常
*
* @param enable
*/
void enableCustomViewRoute(boolean enable);
/**
* 平台:目前仅Android需要此接口
* 应用场景:Android端由于厂商系统版本区别,部分手机应用在后台时需要"悬浮窗"权限或"后台拉起应用"权限,否则无法拉起应用;
* 针对无权限拉不起应用的场景,增加以下接口;
* 当应用在后台收到请求且无权限时,响铃,用户点击通知栏消息或者桌面icon进入应用时,查询通话请求,拉起通话界面
*/
void queryOfflineCalling();
interface TUICallingListener {
/**
* 被叫时请求拉起接听页面
*
* @retrun isAgree 是否同意
*/
boolean shouldShowOnCallView();
/**
* 呼叫开始回调。主叫、被叫均会触发
*
* @param userIDs 本次通话用户id(自己除外)
* @param type 通话类型:视频\音频
* @param role 通话角色:主叫\被叫
*/
void onCallStart(String[] userIDs, TUICalling.Type type, TUICalling.Role role, View tuiCallingView);
/**
* @param userIDs 本次通话用户id(自己除外)
* @param type 通话类型:视频\音频
* @param role 通话角色:主叫\被叫
* @param totalTime 通话时长
*/
void onCallEnd(String[] userIDs, TUICalling.Type type, TUICalling.Role role, long totalTime);
/**
* 通话事件回调
*
* @param event 事件类型
* @param message 事件提示
*/
void onCallEvent(TUICalling.Event event, TUICalling.Type type, TUICalling.Role role, String message);
}
//回调事件
interface TUICallingCallback {
void onCallback(int code, String msg);
}
}
package com.tencent.liteav.trtccalling.model;
import com.tencent.trtc.TRTCCloudDef;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public interface TRTCCallingDelegate {
/**
* sdk内部发生了错误
* @param code 错误码
* @param msg 错误消息
*/
void onError(int code, String msg);
/**
* 被邀请通话回调
* @param sponsor 邀请者
* @param userIdList 同时还被邀请的人
* @param isFromGroup 是否IM群组邀请
* @param callType 邀请类型 1-语音通话,2-视频通话
*/
void onInvited(String sponsor, List<String> userIdList, boolean isFromGroup, int callType);
/**
* 正在IM群组通话时,如果其他与会者邀请他人,会收到此回调
* 例如 A-B-C 正在IM群组中,A邀请[D、E]进入通话,B、C会收到[D、E]的回调
* 如果此时 A 再邀请 F 进入群聊,那么B、C会收到[D、E、F]的回调
* @param userIdList 邀请群组
*/
void onGroupCallInviteeListUpdate(List<String> userIdList);
/**
* 如果有用户同意进入通话,那么会收到此回调
* @param userId 进入通话的用户
*/
void onUserEnter(String userId);
/**
* 如果有用户同意离开通话,那么会收到此回调
* @param userId 离开通话的用户
*/
void onUserLeave(String userId);
/**
* 1. 在C2C通话中,只有发起方会收到拒绝回调
* 例如 A 邀请 B、C 进入通话,B拒绝,A可以收到该回调,但C不行
*
* 2. 在IM群组通话中,所有被邀请人均能收到该回调
* 例如 A 邀请 B、C 进入通话,B拒绝,A、C均能收到该回调
* @param userId 拒绝通话的用户
*/
void onReject(String userId);
/**
* 1. 在C2C通话中,只有发起方会收到无人应答的回调
* 例如 A 邀请 B、C 进入通话,B不应答,A可以收到该回调,但C不行
*
* 2. 在IM群组通话中,所有被邀请人均能收到该回调
* 例如 A 邀请 B、C 进入通话,B不应答,A、C均能收到该回调
* @param userId
*/
void onNoResp(String userId);
/**
* 邀请方忙线
* @param userId 忙线用户
*/
void onLineBusy(String userId);
/**
* 作为被邀请方会收到,收到该回调说明本次通话被取消了
*/
void onCallingCancel();
/**
* 作为被邀请方会收到,收到该回调说明本次通话超时未应答
*/
void onCallingTimeout();
/**
* 收到该回调说明本次通话结束了
*/
void onCallEnd();
/**
* 远端用户开启/关闭了摄像头
* @param userId 远端用户ID
* @param isVideoAvailable true:远端用户打开摄像头 false:远端用户关闭摄像头
*/
void onUserVideoAvailable(String userId, boolean isVideoAvailable);
/**
* 远端用户开启/关闭了麦克风
* @param userId 远端用户ID
* @param isVideoAvailable true:远端用户打开麦克风 false:远端用户关闭麦克风
*/
void onUserAudioAvailable(String userId, boolean isVideoAvailable);
/**
* 用户说话音量回调
* @param volumeMap 音量表,根据每个userid可以获取对应的音量大小,音量最小值0,音量最大值100
*/
void onUserVoiceVolume(Map<String, Integer> volumeMap);
/**
* 网络状态回调。
*
* @param localQuality 上行网络质量。
* @param remoteQuality 下行网络质量。
*/
void onNetworkQuality(TRTCCloudDef.TRTCQuality localQuality, ArrayList<TRTCCloudDef.TRTCQuality> remoteQuality);
/**
* 视频通话切换为语音通话回调
* @param success 切换是否成功,true:切换成功 false:切换失败
* @param message 切换结果信息,如果切换失败,会有切换失败的错误信息
*/
void onSwitchToAudio(boolean success, String message);
}
package com.tencent.liteav.trtccalling.model.impl;
import android.app.Activity;
import android.app.Application;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.tencent.liteav.trtccalling.TUICallingImpl;
import com.tencent.qcloud.tuicore.TUIConstants;
import com.tencent.qcloud.tuicore.TUICore;
import com.tencent.qcloud.tuicore.TUILogin;
/**
* 各模块如果需要初始化,需要实现此类的 init 方法,并在 Manifest 文件中以 ContentProvider 的形式注册。
*/
public final class ServiceInitializer extends ContentProvider {
private static final String TAG = "ServiceInitializer";
/**
* 应用启动时自动调起的初始化方法
*
* @param context applicationContext
*/
public void init(Context context) {
TUICallingService callingService = TUICallingService.sharedInstance();
callingService.init(context);
TUICore.registerService(TUIConstants.TUICalling.SERVICE_NAME, callingService);
TUICore.registerExtension(TUIConstants.TUIChat.EXTENSION_INPUT_MORE_AUDIO_CALL, callingService);
TUICore.registerExtension(TUIConstants.TUIChat.EXTENSION_INPUT_MORE_VIDEO_CALL, callingService);
if (context instanceof Application) {
((Application) context).registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
private int foregroundActivities = 0;
private boolean isChangingConfiguration;
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
}
@Override
public void onActivityStarted(Activity activity) {
foregroundActivities++;
if (foregroundActivities == 1 && !isChangingConfiguration) {
// 应用切到前台
Log.i(TAG, "application enter foreground");
//应用回到前台,需要主动去查询是否有未处理的通话请求
//例如应用在后台时没有拉起应用的权限,当用户听到铃声,从桌面或通知栏进入应用时,主动查询,拉起通话
if (TUILogin.isUserLogined()) {
TUICallingImpl.sharedInstance(context).queryOfflineCalling();
}
}
isChangingConfiguration = false;
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
foregroundActivities--;
isChangingConfiguration = activity.isChangingConfigurations();
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
}
}
/////////////////////////////////////////////////////////////////////////////////
// 以下方法无需重写 //
/////////////////////////////////////////////////////////////////////////////////
@Override
public boolean onCreate() {
Context appContext = getContext().getApplicationContext();
init(appContext);
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
}
package com.tencent.liteav.trtccalling.model.impl;
/**
* 结果回调
*/
public interface TRTCCallingCallback {
//用于返回IM 设置头像和昵称的结果
void onCallback(int code, String msg);
}
package com.tencent.liteav.trtccalling.model.impl;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.tencent.imsdk.v2.V2TIMConversation;
import com.tencent.liteav.trtccalling.R;
import com.tencent.liteav.trtccalling.TUICalling;
import com.tencent.liteav.trtccalling.model.util.TUICallingConstants;
import com.tencent.liteav.trtccalling.TUICallingImpl;
import com.tencent.qcloud.tuicore.TUIConstants;
import com.tencent.qcloud.tuicore.TUICore;
import com.tencent.qcloud.tuicore.interfaces.ITUIExtension;
import com.tencent.qcloud.tuicore.interfaces.ITUINotification;
import com.tencent.qcloud.tuicore.interfaces.ITUIService;
import java.util.HashMap;
import java.util.Map;
/**
* TUICore来调用(如果未引入TUICore模块,请使用TUICallingImpl)
*/
final class TUICallingService implements ITUINotification, ITUIService, ITUIExtension, TUICallingImpl.CallingManagerListener {
private static final String TAG = "TUICallingService";
private static final TUICallingService INSTANCE = new TUICallingService();
private TUICallingImpl mCallingImpl;
static final TUICallingService sharedInstance() {
return INSTANCE;
}
private Context appContext;
private TUICallingService() {
}
public void init(Context context) {
appContext = context;
// 注册IM初始化广播
TUICore.registerEvent(TUIConstants.TUILogin.EVENT_IMSDK_INIT_STATE_CHANGED, TUIConstants.TUILogin.EVENT_SUB_KEY_START_INIT, this);
}
@Override
public Object onCall(String method, Map<String, Object> param) {
Log.d(TAG, String.format("onCall, method=%s, param=%s", method, null == param ? "" : param.toString()));
TUICallingConstants.component = TUICallingConstants.TC_TIMCALLING_COMPONENT;
if (param != null && param.containsKey("component")) {
TUICallingConstants.component = (int) param.get("component");
}
if (null == mCallingImpl) {
Log.e(TAG, "mCallingImpl is null!!!");
return null;
}
if (null != param && TextUtils.equals(TUIConstants.TUICalling.METHOD_NAME_CALL, method)) {
String[] userIDs = (String[]) param.get(TUIConstants.TUICalling.PARAM_NAME_USERIDS);
String typeString = (String) param.get(TUIConstants.TUICalling.PARAM_NAME_TYPE);
String groupID = (String) param.get(TUIConstants.TUICalling.PARAM_NAME_GROUPID);
if (TUIConstants.TUICalling.TYPE_AUDIO.equals(typeString)) {
mCallingImpl.internalCall(userIDs, groupID, TUICalling.Type.AUDIO, TUICalling.Role.CALL);
} else if (TUIConstants.TUICalling.TYPE_VIDEO.equals(typeString)) {
mCallingImpl.internalCall(userIDs, groupID, TUICalling.Type.VIDEO, TUICalling.Role.CALL);
}
}
return null;
}
@Override
public Map<String, Object> onGetExtensionInfo(final String key, Map<String, Object> param) {
Log.d(TAG, String.format("onGetExtensionInfo, key=%s, param=%s", key, null == param ? "" : param.toString()));
TUICallingConstants.component = TUICallingConstants.TC_TIMCALLING_COMPONENT;
if (param != null && param.containsKey("component")) {
TUICallingConstants.component = (int) param.get("component");
}
Context inflateContext = (Context) param.get(TUIConstants.TUIChat.CONTEXT);
if (inflateContext == null) {
inflateContext = appContext;
}
if (inflateContext == null) {
return null;
}
HashMap<String, Object> extensionMap = new HashMap<>();
View unitView = LayoutInflater.from(inflateContext).inflate(R.layout.chat_input_more_actoin, null);
int actionId = 0;
if (key.equals(TUIConstants.TUIChat.EXTENSION_INPUT_MORE_AUDIO_CALL)) {
((ImageView) unitView.findViewById(R.id.imageView)).setImageResource(R.drawable.trtccalling_ic_audio_call);
((TextView) unitView.findViewById(R.id.textView)).setText(inflateContext.getString(R.string.trtccalling_audio_call));
actionId = TUIConstants.TUICalling.ACTION_ID_AUDIO_CALL;
} else if (key.equals(TUIConstants.TUIChat.EXTENSION_INPUT_MORE_VIDEO_CALL)) {
((ImageView) unitView.findViewById(R.id.imageView)).setImageResource(R.drawable.trtccalling_ic_video_call);
((TextView) unitView.findViewById(R.id.textView)).setText(inflateContext.getString(R.string.trtccalling_video_call));
actionId = TUIConstants.TUICalling.ACTION_ID_VIDEO_CALL;
}
final String chatId = (String) param.get(TUIConstants.TUIChat.CHAT_ID);
int chatType = (int) param.get(TUIConstants.TUIChat.CHAT_TYPE);
if (chatType == V2TIMConversation.V2TIM_GROUP) {
unitView.setClickable(false);
} else {
unitView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (null == mCallingImpl) {
Log.e(TAG, "mCallingImpl is null!!!");
return;
}
if (key.equals(TUIConstants.TUIChat.EXTENSION_INPUT_MORE_AUDIO_CALL)) {
mCallingImpl.internalCall(new String[]{chatId}, null, TUICalling.Type.AUDIO, TUICalling.Role.CALL);
} else if (key.equals(TUIConstants.TUIChat.EXTENSION_INPUT_MORE_VIDEO_CALL)) {
mCallingImpl.internalCall(new String[]{chatId}, null, TUICalling.Type.VIDEO, TUICalling.Role.CALL);
}
}
});
}
extensionMap.put(TUIConstants.TUIChat.INPUT_MORE_VIEW, unitView);
extensionMap.put(TUIConstants.TUIChat.INPUT_MORE_ACTION_ID, actionId);
return extensionMap;
}
@Override
public void onEvent(String key, Bundle bundle) {
HashMap<String, Object> param = new HashMap<>();
param.put(TUIConstants.TUICalling.EVENT_KEY_NAME, key);
TUICore.notifyEvent(TUIConstants.TUICalling.EVENT_KEY_CALLING, TUIConstants.TUICalling.EVENT_KEY_CALLING, param);
}
@Override
public void onNotifyEvent(String key, String subKey, Map<String, Object> param) {
if (TUIConstants.TUILogin.EVENT_IMSDK_INIT_STATE_CHANGED.equals(key) && TUIConstants.TUILogin.EVENT_SUB_KEY_START_INIT.equals(subKey)) {
mCallingImpl = (TUICallingImpl) TUICallingImpl.sharedInstance(appContext);
mCallingImpl.setCallingManagerListener(this);
}
}
}
package com.tencent.liteav.trtccalling.model.impl;
public class UserModel {
public String userId;
public String userAvatar;
public String userName;
@Override
public String toString() {
return "UserModel{"
+ "userId= '" + userId + '\''
+ ", userAvatar= '" + userAvatar + '\''
+ ", userName= '" + userName + '\''
+ '}';
}
}
package com.tencent.liteav.trtccalling.model.impl.base;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义消息的bean实体,用来与json的相互转化
*/
public class CallModel implements Cloneable, Serializable {
private static final String TAG = CallModel.class.getSimpleName();
public static String KEY_VERSION = "version";
public static String KEY_PLATFORM = "platform";
public static String KEY_BUSINESS_ID = "businessID";
public static String KEY_DATA = "data";
public static String KEY_ROOM_ID = "room_id";
public static String KEY_CMD = "cmd";
public static String KEY_USERIDS = "userIDs";
public static String KEY_MESSAGE = "message";
public static String KEY_CALLACTION = "call_action";
public static String KEY_CALLID = "callid";
public static String KEY_USER = "user";
public static final int VALUE_VERSION = 4;
public static final String VALUE_BUSINESS_ID = "av_call"; //calling场景
public static final String VALUE_PLATFORM = "Android"; //当前平台
public static final String VALUE_CMD_VIDEO_CALL = "videoCall"; //视频电话呼叫
public static final String VALUE_CMD_AUDIO_CALL = "audioCall"; //语音电话呼叫
public static final String VALUE_CMD_HAND_UP = "hangup"; //挂断
public static final String VALUE_CMD_SWITCH_TO_AUDIO = "switchToAudio"; //切换为语音通话
public static final String VALUE_MSG_LINE_BUSY = "lineBusy"; //忙线
public static final String VALUE_MSG_SYNC_INFO = "sync_info"; //C2C多人通话,主叫向其他人同步信息
/**
* 系统错误
*/
public static final int VIDEO_CALL_ACTION_ERROR = -1;
/**
* 未知信令
*/
public static final int VIDEO_CALL_ACTION_UNKNOWN = 0;
/**
* 正在呼叫
*/
public static final int VIDEO_CALL_ACTION_DIALING = 1;
/**
* 发起人取消
*/
public static final int VIDEO_CALL_ACTION_SPONSOR_CANCEL = 2;
/**
* 拒接电话
*/
public static final int VIDEO_CALL_ACTION_REJECT = 3;
/**
* 无人接听
*/
public static final int VIDEO_CALL_ACTION_SPONSOR_TIMEOUT = 4;
/**
* 挂断
*/
public static final int VIDEO_CALL_ACTION_HANGUP = 5;
/**
* 电话占线
*/
public static final int VIDEO_CALL_ACTION_LINE_BUSY = 6;
/**
* 接听电话
*/
public static final int VIDEO_CALL_ACTION_ACCEPT = 7;
/**
* 切换语音通话
*/
public static final int VIDEO_CALL_SWITCH_TO_AUDIO_CALL = 8;
/**
* 接受切换为语音通话
*/
public static final int VIDEO_CALL_ACTION_ACCEPT_SWITCH_TO_AUDIO = 9;
/**
* 拒绝切换为语音通话
*/
public static final int VIDEO_CALL_ACTION_REJECT_SWITCH_TO_AUDIO = 10;
//兼容老版本字段,待废弃字段
public static String SIGNALING_EXTRA_KEY_CALL_TYPE = "call_type";
public static String SIGNALING_EXTRA_KEY_ROOM_ID = "room_id";
public static String SIGNALING_EXTRA_KEY_LINE_BUSY = "line_busy";
public static String SIGNALING_EXTRA_KEY_CALL_END = "call_end";
public static String SIGNALING_EXTRA_KEY_SWITCH_AUDIO_CALL = "switch_to_audio_call";
@SerializedName("version")
public int version = 0;
/**
* 表示一次通话的唯一ID
*/
@SerializedName("call_id")
public String callId;
/**
* TRTC的房间号
*/
@SerializedName("room_id")
public int roomId = 0;
/**
* IM的群组id,在群组内发起通话时使用
*/
@SerializedName("group_id")
public String groupId = "";
/**
* 信令动作
*/
@SerializedName("action")
public int action = VIDEO_CALL_ACTION_UNKNOWN;
/**
* 通话类型
* 0-未知
* 1-语音通话
* 2-视频通话
*/
@SerializedName("call_type")
public int callType = 0;
/**
* 正在邀请的列表
*/
@SerializedName("invited_list")
public List<String> invitedList;
@SerializedName("duration")
public int duration = 0;
@SerializedName("code")
public int code = 0;
public long timestamp;
public String sender;
// 超时时间,单位秒
public int timeout;
public String data;
@Override
public Object clone() {
CallModel callModel = null;
try {
callModel = (CallModel) super.clone();
if (invitedList != null) {
callModel.invitedList = new ArrayList<>(invitedList);
}
} catch (CloneNotSupportedException e) {
e.printStackTrace();
TRTCLogger.w(TAG, "clone: " + e.getLocalizedMessage());
}
return callModel;
}
@Override
public String toString() {
return "CallModel{" +
"version=" + version +
", callId='" + callId + '\'' +
", roomId=" + roomId +
", groupId='" + groupId + '\'' +
", action=" + action +
", callType=" + callType +
", invitedList=" + invitedList +
", duration=" + duration +
", code=" + code +
", timestamp=" + timestamp +
", sender=" + sender +
'}';
}
}
\ No newline at end of file
package com.tencent.liteav.trtccalling.model.impl.base;
import android.text.TextUtils;
import com.tencent.imsdk.v2.V2TIMManager;
import com.tencent.imsdk.v2.V2TIMUserFullInfo;
import com.tencent.imsdk.v2.V2TIMValueCallback;
import com.tencent.liteav.trtccalling.model.impl.UserModel;
import java.util.ArrayList;
import java.util.List;
public class CallingInfoManager {
private static CallingInfoManager sInstance;
private static final String TAG = "CallingInfoManager";
public static CallingInfoManager getInstance() {
if (sInstance == null) {
synchronized (CallingInfoManager.class) {
if (sInstance == null) {
sInstance = new CallingInfoManager();
}
}
}
return sInstance;
}
public void getUserInfoByUserId(final String userId, final UserCallback callback) {
if (TextUtils.isEmpty(userId)) {
TRTCLogger.e(TAG, "get user info list fail, user list is empty.");
if (callback != null) {
callback.onFailed(-1, "get user info list fail, user list is empty.");
}
return;
}
List<String> userList = new ArrayList<>();
userList.add(userId);
TRTCLogger.i(TAG, "get user info list " + userList);
V2TIMManager.getInstance().getUsersInfo(userList, new V2TIMValueCallback<List<V2TIMUserFullInfo>>() {
@Override
public void onError(int errorCode, String errorMsg) {
TRTCLogger.e(TAG, "getUsersInfo fail, code: " + errorCode + ", errorMsg: " + errorMsg);
if (callback != null) {
callback.onFailed(errorCode, errorMsg);
}
}
@Override
public void onSuccess(List<V2TIMUserFullInfo> v2TIMUserFullInfos) {
if (v2TIMUserFullInfos == null || v2TIMUserFullInfos.size() <= 0) {
TRTCLogger.d(TAG, "getUserInfoByUserId result ignored");
if (null != callback) {
callback.onFailed(-1, "getUserInfoByUserId result ignored");
}
return;
}
List<UserModel> list = new ArrayList<>();
for (int i = 0; i < v2TIMUserFullInfos.size(); i++) {
UserModel model = new UserModel();
model.userName = v2TIMUserFullInfos.get(i).getNickName();
model.userId = v2TIMUserFullInfos.get(i).getUserID();
model.userAvatar = v2TIMUserFullInfos.get(i).getFaceUrl();
list.add(model);
TRTCLogger.d(TAG, String.format("getUserInfoByUserId, userId=%s, userName=%s, userAvatar=%s",
model.userId, model.userName, model.userAvatar));
if (TextUtils.isEmpty(model.userName)) {
model.userName = model.userId;
}
}
if (callback != null) {
callback.onSuccess(list.get(0));
}
}
});
}
// 通过userid/phone获取用户信息回调
public interface UserCallback {
void onSuccess(UserModel model);
void onFailed(int code, String msg);
}
}
package com.tencent.liteav.trtccalling.model.impl.base;
public class MessageCustom {
public static final String BUSINESS_ID_GROUP_CREATE = "group_create";
public static final String BUSINESS_ID_AV_CALL = "av_call";
public int version = 0;
public String businessID;
public String opUser;
public String content;
}
package com.tencent.liteav.trtccalling.model.impl.base;
import com.tencent.imsdk.v2.V2TIMConversation;
public class OfflineMessageBean {
public static final int DEFAULT_VERSION = 1;
public static final int REDIRECT_ACTION_CHAT = 1;
public static final int REDIRECT_ACTION_CALL = 2;
public int version = DEFAULT_VERSION;
public int chatType = V2TIMConversation.V2TIM_C2C;
public int action = REDIRECT_ACTION_CHAT;
public String sender = "";
public String nickname = "";
public String faceUrl = "";
public String content = "";
// 发送时间戳,单位秒
public long sendTime = 0;
@Override
public String toString() {
return "OfflineMessageBean{" +
"version=" + version +
", chatType='" + chatType + '\'' +
", action=" + action +
", sender=" + sender +
", nickname=" + nickname +
", faceUrl=" + faceUrl +
", content=" + content +
", sendTime=" + sendTime +
'}';
}
}
\ No newline at end of file
package com.tencent.liteav.trtccalling.model.impl.base;
/**
* 为什么离线消息要做两层嵌套,主要是因为后台在携带离线数据时的包装不一样
*
* 比如我们发消息的时候想在V2TIMOfflinePushInfo.setExt设置的数据为{"a":1,"b":2}
* 那后台对不同的厂商会有不同的包装:
*
* 小米:{"ext":"{\"a\":1, \"b\":2}"},小米解析ext字段出来就可以
* OPPO:没有用ext包装,所以oppo收到数据时用bundle.keySet()解析为两个key:a和b,值分别为1和2,无法简单转化为bean
*
* 所以现在的做法时,在发送离线消息时,统一包装为{"entity":"xxxxxx"},实际内容放在entity中,这样对应各个平台的解析:
*
* 小米:{"ext":"{\"entity\": \"xxxxxx\"},小米解析ext字段,用容器类OfflineMessageContainerBean获取到entity
* OPPO:用bundle.keySet()解析出entity的key,直接就可以获取实际消息OfflineMessageBean
*/
public class OfflineMessageContainerBean {
public OfflineMessageBean entity;
}
package com.tencent.liteav.trtccalling.model.impl.base;
import java.util.List;
public class SignallingData {
private int version;
private String businessID;
private String platform;
private String extInfo;
private DataInfo data;
//多人通话custom message增加字段
private int call_action;
private String callid;
private String user;
public int getCallAction() {
return call_action;
}
public void setCallAction(int call_action) {
this.call_action = call_action;
}
public String getCallid() {
return callid;
}
public void setcallid(String callid) {
this.callid = callid;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
//兼容老版本字段,待废弃字段
private int call_type;
private int room_id;
private int call_end;
private String switch_to_audio_call;
private String line_busy;
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
public String getBusinessID() {
return businessID;
}
public void setBusinessID(String businessID) {
this.businessID = businessID;
}
public String getPlatform() {
return platform;
}
public void setPlatform(String platform) {
this.platform = platform;
}
public int getCallType() {
return call_type;
}
public void setCallType(int callType) {
this.call_type = callType;
}
public int getRoomId() {
return room_id;
}
public void setRoomId(int roomId) {
this.room_id = roomId;
}
public int getCallEnd() {
return call_end;
}
public void setCallEnd(int callEnd) {
this.call_end = callEnd;
}
public String getSwitchToAudioCall() {
return switch_to_audio_call;
}
public void setSwitchToAudioCall(String switchToAudioCall) {
this.switch_to_audio_call = switchToAudioCall;
}
public String getLineBusy() {
return line_busy;
}
public void setLineBusy(String lineBusy) {
this.line_busy = lineBusy;
}
public DataInfo getData() {
return data;
}
public void setData(DataInfo data) {
this.data = data;
}
public static class DataInfo {
private int room_id;
private String cmd;
private String cmdInfo;
private String message;
private List<String> userIDs;
public int getRoomID() {
return room_id;
}
public void setRoomID(int roomID) {
this.room_id = roomID;
}
public String getCmd() {
return cmd;
}
public void setCmd(String cmd) {
this.cmd = cmd;
}
public String getCmdInfo() {
return cmdInfo;
}
public void setCmdInfo(String cmdInfo) {
this.cmdInfo = cmdInfo;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public List<String> getUserIDs() {
return userIDs;
}
public void setUserIDs(List<String> userIDs) {
this.userIDs = userIDs;
}
}
}
package com.tencent.liteav.trtccalling.model.impl.base;
import com.tencent.liteav.trtccalling.model.TRTCCallingDelegate;
import com.tencent.trtc.TRTCCloudDef;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* 这个类用来保存所有的监听个回调
*/
public class TRTCInternalListenerManager implements TRTCCallingDelegate {
private List<WeakReference<TRTCCallingDelegate>> mWeakReferenceList;
public TRTCInternalListenerManager() {
mWeakReferenceList = new ArrayList<>();
}
public void addDelegate(TRTCCallingDelegate listener) {
WeakReference<TRTCCallingDelegate> listenerWeakReference = new WeakReference<>(listener);
mWeakReferenceList.add(listenerWeakReference);
}
public void removeDelegate(TRTCCallingDelegate listener) {
Iterator iterator = mWeakReferenceList.iterator();
while (iterator.hasNext()) {
WeakReference<TRTCCallingDelegate> reference = (WeakReference<TRTCCallingDelegate>) iterator.next();
if (reference.get() == null) {
iterator.remove();
continue;
}
if (reference.get() == listener) {
iterator.remove();
}
}
}
@Override
public void onError(int code, String msg) {
for (WeakReference<TRTCCallingDelegate> reference : mWeakReferenceList) {
TRTCCallingDelegate listener = reference.get();
if (listener != null) {
listener.onError(code, msg);
}
}
}
@Override
public void onInvited(String sponsor, List<String> userIdList, boolean isFromGroup, int callType) {
for (WeakReference<TRTCCallingDelegate> reference : mWeakReferenceList) {
TRTCCallingDelegate listener = reference.get();
if (listener != null) {
listener.onInvited(sponsor, userIdList, isFromGroup, callType);
}
}
}
@Override
public void onGroupCallInviteeListUpdate(List<String> userIdList) {
for (WeakReference<TRTCCallingDelegate> reference : mWeakReferenceList) {
TRTCCallingDelegate listener = reference.get();
if (listener != null) {
listener.onGroupCallInviteeListUpdate(userIdList);
}
}
}
@Override
public void onUserEnter(String userId) {
for (WeakReference<TRTCCallingDelegate> reference : mWeakReferenceList) {
TRTCCallingDelegate listener = reference.get();
if (listener != null) {
listener.onUserEnter(userId);
}
}
}
@Override
public void onUserLeave(String userId) {
for (WeakReference<TRTCCallingDelegate> reference : mWeakReferenceList) {
TRTCCallingDelegate listener = reference.get();
if (listener != null) {
listener.onUserLeave(userId);
}
}
}
@Override
public void onReject(String userId) {
for (WeakReference<TRTCCallingDelegate> reference : mWeakReferenceList) {
TRTCCallingDelegate listener = reference.get();
if (listener != null) {
listener.onReject(userId);
}
}
}
@Override
public void onNoResp(String userId) {
for (WeakReference<TRTCCallingDelegate> reference : mWeakReferenceList) {
TRTCCallingDelegate listener = reference.get();
if (listener != null) {
listener.onNoResp(userId);
}
}
}
@Override
public void onLineBusy(String userId) {
for (WeakReference<TRTCCallingDelegate> reference : mWeakReferenceList) {
TRTCCallingDelegate listener = reference.get();
if (listener != null) {
listener.onLineBusy(userId);
}
}
}
@Override
public void onCallingCancel() {
for (WeakReference<TRTCCallingDelegate> reference : mWeakReferenceList) {
TRTCCallingDelegate listener = reference.get();
if (listener != null) {
listener.onCallingCancel();
}
}
}
@Override
public void onCallingTimeout() {
for (WeakReference<TRTCCallingDelegate> reference : mWeakReferenceList) {
TRTCCallingDelegate listener = reference.get();
if (listener != null) {
listener.onCallingTimeout();
}
}
}
@Override
public void onCallEnd() {
for (WeakReference<TRTCCallingDelegate> reference : mWeakReferenceList) {
TRTCCallingDelegate listener = reference.get();
if (listener != null) {
listener.onCallEnd();
}
}
}
@Override
public void onUserVideoAvailable(String userId, boolean isVideoAvailable) {
for (WeakReference<TRTCCallingDelegate> reference : mWeakReferenceList) {
TRTCCallingDelegate listener = reference.get();
if (listener != null) {
listener.onUserVideoAvailable(userId, isVideoAvailable);
}
}
}
@Override
public void onUserAudioAvailable(String userId, boolean isVideoAvailable) {
for (WeakReference<TRTCCallingDelegate> reference : mWeakReferenceList) {
TRTCCallingDelegate listener = reference.get();
if (listener != null) {
listener.onUserAudioAvailable(userId, isVideoAvailable);
}
}
}
@Override
public void onUserVoiceVolume(Map<String, Integer> volumeMap) {
for (WeakReference<TRTCCallingDelegate> reference : mWeakReferenceList) {
TRTCCallingDelegate listener = reference.get();
if (listener != null) {
listener.onUserVoiceVolume(volumeMap);
}
}
}
@Override
public void onNetworkQuality(TRTCCloudDef.TRTCQuality localQuality, ArrayList<TRTCCloudDef.TRTCQuality> remoteQuality) {
for (WeakReference<TRTCCallingDelegate> reference : mWeakReferenceList) {
TRTCCallingDelegate listener = reference.get();
if (listener != null) {
listener.onNetworkQuality(localQuality, remoteQuality);
}
}
}
@Override
public void onSwitchToAudio(boolean success, String message) {
for (WeakReference<TRTCCallingDelegate> reference : mWeakReferenceList) {
TRTCCallingDelegate listener = reference.get();
if (listener != null) {
listener.onSwitchToAudio(success, message);
}
}
}
}
package com.tencent.liteav.trtccalling.model.impl.base;
import com.tencent.liteav.basic.log.TXCLog;
public class TRTCLogger {
public static void e(String tag, String message) {
TXCLog.e(tag, message);
}
public static void w(String tag, String message) {
TXCLog.w(tag, message);
}
public static void i(String tag, String message) {
TXCLog.i(tag, message);
}
public static void d(String tag, String message) {
TXCLog.d(tag, message);
}
}
package com.tencent.liteav.trtccalling.model.util;
public interface AvatarConstant {
String USER_AVATAR_ARRAY [] = {
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar1.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar2.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar3.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar4.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar5.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar6.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar7.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar8.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar9.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar10.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar11.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar12.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar13.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar14.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar15.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar16.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar17.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar18.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar19.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar20.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar21.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar22.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar23.png",
"https://liteav.sdk.qcloud.com/app/res/picture/voiceroom/avatar/user_avatar24.png",
};
}
package com.tencent.liteav.trtccalling.model.util;
import android.text.TextUtils;
import com.tencent.qcloud.tuicore.util.TUIBuild;
public class BrandUtil {
private static String mBrand = "";
private static String mManufacturer = "";
private static void init() {
if (TextUtils.isEmpty(mBrand)) {
mBrand = TUIBuild.getBrand();
}
if (TextUtils.isEmpty(mManufacturer)) {
mManufacturer = TUIBuild.getManufacturer();
}
}
/**
* 判断是否为小米设备
*/
public static boolean isBrandXiaoMi() {
init();
return "xiaomi".equalsIgnoreCase(mBrand)
|| "xiaomi".equalsIgnoreCase(mManufacturer);
}
/**
* 判断是否为华为设备
*/
public static boolean isBrandHuawei() {
init();
return "huawei".equalsIgnoreCase(mBrand)
|| "huawei".equalsIgnoreCase(mManufacturer);
}
/**
* 判断是否为魅族设备
*/
public static boolean isBrandMeizu() {
init();
return "meizu".equalsIgnoreCase(mBrand)
|| "meizu".equalsIgnoreCase(mManufacturer)
|| "22c4185e".equalsIgnoreCase(mBrand);
}
/**
* 判断是否是 oppo 设备, 包含子品牌
*
* @return
*/
public static boolean isBrandOppo() {
init();
return "oppo".equalsIgnoreCase(mBrand) ||
"realme".equalsIgnoreCase(mBrand) ||
"oneplus".equalsIgnoreCase(mBrand) ||
"oppo".equalsIgnoreCase(mManufacturer) ||
"realme".equalsIgnoreCase(mManufacturer) ||
"oneplus".equalsIgnoreCase(mManufacturer);
}
/**
* 判断是否是vivo设备
*
* @return
*/
public static boolean isBrandVivo() {
init();
return "vivo".equalsIgnoreCase(mBrand)
|| "vivo".equalsIgnoreCase(mManufacturer);
}
}
package com.tencent.liteav.trtccalling.model.util;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.widget.ImageView;
import androidx.annotation.DrawableRes;
import androidx.annotation.RawRes;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
import com.bumptech.glide.request.RequestOptions;
import java.security.MessageDigest;
import java.util.concurrent.ExecutionException;
public class ImageLoader {
private static int radius = 0; //TRTC默认图片圆角为0dp
public static void clear(Context context, ImageView imageView) {
Glide.with(context).clear(imageView);
}
public static void loadImage(Context context, ImageView imageView, String url) {
loadImage(context, imageView, url, 0, radius);
}
public static void loadImage(Context context, ImageView imageView, String url, @DrawableRes int errorResId) {
loadImage(context, imageView, url, errorResId, radius);
}
public static void loadImage(Context context, ImageView imageView, String url, @DrawableRes int errorResId, int radius) {
if (TextUtils.isEmpty(url)) {
if (imageView != null && errorResId != 0) {
imageView.setImageResource(errorResId);
}
return;
}
Glide.with(context).load(url).error(loadTransform(context, errorResId, radius)).into(imageView);
}
public static void loadImage(Context context, ImageView imageView, @RawRes @DrawableRes Integer resourceId) {
Glide.with(context).load(resourceId).into(imageView);
}
public static Bitmap getImage(Context context, String url, int width, int height) {
if (TextUtils.isEmpty(url)) {
return null;
}
try {
Bitmap bitmap = Glide.with(context)
.asBitmap()
.load(url)
.into(width, height)
.get();
return bitmap;
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
public static void loadImageThumbnail(Context context, ImageView imageView, String url, @DrawableRes int resourceId, int radius) {
Glide.with(context).load(url)
.apply(new RequestOptions().placeholder(resourceId).error(resourceId).centerCrop()
.transform(new GlideRoundTransform(imageView.getContext(), radius)))
.thumbnail(loadTransform(imageView.getContext(), resourceId, radius))
.into(imageView);
}
public static void loadGifImage(Context context, ImageView imageView, String url) {
loadGifImage(context, imageView, url, 0);
}
public static void loadGifImage(Context context, ImageView imageView, String url, @DrawableRes int errorResId) {
if (TextUtils.isEmpty(url)) {
if (imageView != null && errorResId != 0) {
imageView.setImageResource(errorResId);
}
return;
}
Glide.with(context).asGif().load(url).into(imageView);
}
public static void loadGifImage(Context context, ImageView imageView, @RawRes @DrawableRes int resourceId) {
Glide.with(context).asGif().load(resourceId).into(imageView);
}
private static RequestBuilder<Drawable> loadTransform(Context context, @DrawableRes int placeholderId, int radius) {
return Glide.with(context).load(placeholderId)
.apply(new RequestOptions().centerCrop().transform(new GlideRoundTransform(context, radius)));
}
public static class GlideRoundTransform extends BitmapTransformation {
private static float radius = 0f;
public GlideRoundTransform(Context context, int dp) {
radius = Resources.getSystem().getDisplayMetrics().density * dp;
}
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
return roundCrop(pool, toTransform);
}
private static Bitmap roundCrop(BitmapPool pool, Bitmap source) {
if (source == null) return null;
Bitmap result = pool.get(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);
if (result == null) {
result = Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);
}
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
paint.setShader(new BitmapShader(source, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
paint.setAntiAlias(true);
RectF rectF = new RectF(0f, 0f, source.getWidth(), source.getHeight());
canvas.drawRoundRect(rectF, radius, radius, paint);
return result;
}
@Override
public void updateDiskCacheKey(MessageDigest messageDigest) {
}
}
}
package com.tencent.liteav.trtccalling.model.util;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.text.TextUtils;
import android.util.Log;
import com.tencent.liteav.trtccalling.model.impl.base.TRTCLogger;
import java.io.File;
import java.io.IOException;
public class MediaPlayHelper {
private static final String TAG = "MediaPlayHelper";
private Context mContext;
private final MediaPlayer mMediaPlayer;
private Handler mHandler;
private int mResId; //资源ID,apk内置资源
private String mResPath; //资源路径,apk沙盒地址,例如:/sdcard/android/data/com.tencent.trtc/files/rain.mp3
private Uri mResUrl; //网络资源,例如:https://web.sdk.qcloud.com/component/TUIKit/assets/uni-app/calling-bell-1.mp3
public MediaPlayHelper(Context context) {
mContext = context;
mMediaPlayer = new MediaPlayer();
mResId = -1;
mResPath = "";
}
public void start(String path) {
start(path, -1, 0);
}
public void start(int resId) {
start(resId, 0);
}
public void start(int resId, long duration) {
start("", resId, duration);
}
private void start(String resPath, final int resId, long duration) {
preHandler();
if (TextUtils.isEmpty(resPath) && (-1 == resId)) {
TRTCLogger.d(TAG, " empty source ,please set media resource");
return;
}
if ((-1 != resId && (mResId == resId)) || (!TextUtils.isEmpty(resPath) && TextUtils.equals(mResPath, resPath))) {
TRTCLogger.d(TAG, "the same media source, ignore");
return;
}
AssetFileDescriptor afd0 = null;
TRTCLogger.d(TAG, " music start resPath: " + resPath + " ,resId: " + resId);
if (!TextUtils.isEmpty(resPath) && isUrl(resPath)) {
Uri tempUrl = Uri.parse(resPath);
if (tempUrl.equals(mResUrl)) {
TRTCLogger.d(TAG, " the same resUrl, ignore");
return;
}
mResUrl = Uri.parse(resPath);
} else if (!TextUtils.isEmpty(resPath) && new File(resPath).exists()) {
mResPath = resPath;
} else if (-1 != resId) {
mResId = resId;
afd0 = mContext.getResources().openRawResourceFd(resId);
if (afd0 == null) {
return;
}
}
final AssetFileDescriptor afd = afd0;
final Uri finalResUrl = mResUrl;
mHandler.post(new Runnable() {
@Override
public void run() {
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.stop();
}
mMediaPlayer.setOnCompletionListener(null);
mMediaPlayer.reset();
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
try {
if (null != afd) {
TRTCLogger.d(TAG, "play resId:" + resId);
mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
} else if (null != finalResUrl) {
TRTCLogger.d(TAG, "play resUrl:" + finalResUrl);
mMediaPlayer.setDataSource(mContext, finalResUrl);
} else if (!TextUtils.isEmpty(mResPath)) {
TRTCLogger.d(TAG, "play resPath:" + mResPath);
mMediaPlayer.setDataSource(mResPath);
} else {
TRTCLogger.d(TAG, "invalid Source");
return;
}
} catch (Exception e) {
TRTCLogger.e(TAG, Log.getStackTraceString(e));
}
mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
stop();
}
});
try {
mMediaPlayer.prepare();
} catch (IOException e) {
TRTCLogger.e(TAG, Log.getStackTraceString(e));
}
mMediaPlayer.start();
}
});
if (duration > 0) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
stop();
}
}, duration);
}
}
private boolean isUrl(String url) {
return url.startsWith("http://") || url.startsWith("https://");
}
private void preHandler() {
if (null != mHandler) {
return;
}
HandlerThread thread = new HandlerThread("Handler-MediaPlayer");
thread.start();
mHandler = new Handler(thread.getLooper());
}
public int getResId() {
return mResId;
}
public void stop() {
if (null == mHandler) {
TRTCLogger.d(TAG, "mediaPlayer not start");
return;
}
if ((-1 == getResId()) && TextUtils.isEmpty(mResPath) && (null == mResUrl)) {
TRTCLogger.d(TAG, "cannot stop empty resource");
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.stop();
}
mResId = -1;
mResPath = "";
mResUrl = null;
}
});
}
}
package com.tencent.liteav.trtccalling.model.util;
import android.app.AppOpsManager;
import android.content.Context;
import android.database.Cursor;
import android.graphics.PixelFormat;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import androidx.annotation.RequiresApi;
import java.lang.reflect.Method;
public class PermissionUtil {
private static final String TAG = "PermissionUtil";
//是否已经有权限了,或者是否已经给用户提示过了
public static boolean mHasPermissionOrHasHinted = false;
public static boolean hasPermission(Context context) {
if (BrandUtil.isBrandXiaoMi()) {
if (Build.VERSION.SDK_INT >= 30 && !Settings.canDrawOverlays(context)) {
return false;
}
return isXiaomiBgStartPermissionAllowed(context);
} else if (BrandUtil.isBrandVivo()) {
return isVivoBgStartPermissionAllowed(context);
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return Settings.canDrawOverlays(context);
} else {
return hasPermissionBelowMarshmallow(context);
}
}
}
public static boolean hasPermissionOnActivityResult(Context context) {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) {
return hasPermissionForO(context);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return Settings.canDrawOverlays(context);
} else {
return hasPermissionBelowMarshmallow(context);
}
}
/**
* 6.0以下判断是否有权限
* 理论上6.0以上才需处理权限,但有的国内rom在6.0以下就添加了权限
*/
static boolean hasPermissionBelowMarshmallow(Context context) {
try {
AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
Method dispatchMethod = AppOpsManager.class.getMethod("checkOp", int.class, int.class, String.class);
return AppOpsManager.MODE_ALLOWED == (Integer) dispatchMethod.invoke(
manager, 24, Binder.getCallingUid(), context.getApplicationContext().getPackageName());
} catch (Exception e) {
return false;
}
}
/**
* 用于判断8.0时是否有权限,仅用于OnActivityResult
*/
@RequiresApi(api = Build.VERSION_CODES.M)
private static boolean hasPermissionForO(Context context) {
try {
WindowManager mgr = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
if (mgr == null) return false;
View viewToAdd = new View(context);
WindowManager.LayoutParams params = new WindowManager.LayoutParams(0, 0,
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSPARENT);
viewToAdd.setLayoutParams(params);
mgr.addView(viewToAdd, params);
mgr.removeView(viewToAdd);
return true;
} catch (Exception e) {
Log.e(TAG, "hasPermissionForO e:" + e.toString());
}
return false;
}
//小米后台拉起应用权限是否开启
public static boolean isXiaomiBgStartPermissionAllowed(Context context) {
AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
int op = 10021;
try {
Method method = appOpsManager.getClass().getMethod("checkOpNoThrow",
new Class[]{int.class, int.class, String.class});
method.setAccessible(true);
int result = (int) method.invoke(appOpsManager, op, android.os.Process.myUid(), context.getPackageName());
return AppOpsManager.MODE_ALLOWED == result;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
//Vivo后台拉起应用权限是否开启
private static boolean isVivoBgStartPermissionAllowed(Context context) {
try {
Uri uri = Uri.parse("content://com.vivo.permissionmanager.provider.permission/start_bg_activity");
Cursor cursor = context.getContentResolver().query(uri, null, "pkgname = ?",
new String[]{context.getPackageName()},
null);
if (cursor.moveToFirst()) {
int state = cursor.getInt(cursor.getColumnIndex("currentstate"));
return 0 == state;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
package com.tencent.liteav.trtccalling.model.util;
public class TUICallingConstants {
public static final String METHOD_NAME_CALL = "call";
public static final String METHOD_NAME_RECEIVEAPNSCALLED = "receiveAPNSCalled";
public static final String PARAM_NAME_TYPE = "type";
public static final String PARAM_NAME_ROLE = "role";
public static final String PARAM_NAME_USERIDS = "userIDs";
public static final String PARAM_NAME_GROUPID = "groupId";
public static final String PARAM_NAME_SPONSORID = "sponsorID";
public static final String PARAM_NAME_ISFROMGROUP = "isFromGroup";
public static final String PARAM_NAME_FLOATWINDOW = "enableFloatWindow";
public static final String KEY_CALL_TYPE = "call_type";
public static final String KEY_GROUP_ID = "group_id";
public static final String KEY_INVITED_LIST = "invited_list";
public static final String KEY_CALL_ID = "call_id";
public static final String KEY_ROOM_ID = "room_id";
public static final String TYPE_AUDIO = "audio";
public static final String TYPE_VIDEO = "video";
public static final String EVENT_KEY_CALLING = "calling";
public static final String EVENT_KEY_NAME = "event_name";
public static final String EVENT_ACTIVE_HANGUP = "active_hangup";
//onCallEvent常用类型定义
public static final String EVENT_CALL_HANG_UP = "Hangup";
public static final String EVENT_CALL_LINE_BUSY = "LineBusy";
public static final String EVENT_CALL_CNACEL = "Cancel";
public static final String EVENT_CALL_TIMEOUT = "Timeout";
public static final String EVENT_CALL_NO_RESP = "NoResp";
public static final String EVENT_CALL_SWITCH_TO_AUDIO = "SwitchToAudio";
public static final int TC_TUICALLING_COMPONENT = 3;
public static final int TC_TIMCALLING_COMPONENT = 10;
public static final int TC_TRTC_FRAMEWORK = 1;
public static int component = TC_TUICALLING_COMPONENT;
}
package com.tencent.liteav.trtccalling.ui.audiocall.audiolayout;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.AttributeSet;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.tencent.liteav.trtccalling.R;
import com.tencent.liteav.trtccalling.ui.common.RoundCornerImageView;
/**
* 通话界面中,显示单个用户头像的自定义布局
*/
public class TRTCAudioLayout extends RelativeLayout {
private static final int MIN_AUDIO_VOLUME = 10;
private RoundCornerImageView mImageHead;
private TextView mTextName;
private ImageView mImageAudioInput;
private ImageView mImgLoading;
public TRTCAudioLayout(Context context) {
this(context, null);
}
public TRTCAudioLayout(Context context, AttributeSet attrs) {
super(context, attrs);
inflate(context, R.layout.trtccalling_audiocall_item_user_layout, this);
initView();
}
private void initView() {
mImageHead = (RoundCornerImageView) findViewById(R.id.img_head);
mTextName = (TextView) findViewById(R.id.tv_name);
mImageAudioInput = (ImageView) findViewById(R.id.iv_audio_input);
mImgLoading = (ImageView) findViewById(R.id.img_loading);
}
public void setAudioVolume(int vol) {
if (vol > MIN_AUDIO_VOLUME) {
mImageAudioInput.setVisibility(VISIBLE);
} else {
mImageAudioInput.setVisibility(GONE);
}
}
public void setUserName(String userName) {
mTextName.setText(userName);
}
public RoundCornerImageView getImageView() {
return mImageHead;
}
public void startLoading() {
mImgLoading.setVisibility(VISIBLE);
}
public void stopLoading() {
mImgLoading.setVisibility(GONE);
}
}
package com.tencent.liteav.trtccalling.ui.audiocall.audiolayout;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
import com.tencent.liteav.trtccalling.model.impl.base.TRTCLogger;
import java.util.ArrayList;
import java.util.Iterator;
/**
* 通话过程中,用来显示/管理所有用户头像的{@link TRTCAudioLayout}自定义布局
*/
public class TRTCAudioLayoutManager extends RelativeLayout {
private static final String TAG = "TRTCAudioLayoutManager";
public static final int MAX_USER = 9;
private String mSelfUserId;
private Context mContext;
private int mCount = 0;
private boolean mInitParam = false;
private ArrayList<LayoutParams> mGrid1ParamList;
private ArrayList<LayoutParams> mGrid2ParamList;
private ArrayList<LayoutParams> mGrid3ParamList;
private ArrayList<LayoutParams> mGrid4ParamList;
private ArrayList<LayoutParams> mGrid9ParamList;
private ArrayList<TRTCLayoutEntity> mLayoutEntityList;
public TRTCAudioLayoutManager(Context context) {
super(context);
initView(context);
}
public TRTCAudioLayoutManager(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public TRTCAudioLayoutManager(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context) {
TRTCLogger.i(TAG, "initView");
mContext = context;
// 做成正方形
mLayoutEntityList = new ArrayList<TRTCLayoutEntity>();
this.post(new Runnable() {
@Override
public void run() {
makeGirdLayout(true);
}
});
}
public void setMySelfUserId(String userId) {
mSelfUserId = userId;
}
/**
* 根据 userId 找到已经分配的 View
*/
public TRTCAudioLayout findAudioCallLayout(String userId) {
if (userId == null) return null;
for (TRTCLayoutEntity layoutEntity : mLayoutEntityList) {
if (layoutEntity.userId.equals(userId)) {
return layoutEntity.layout;
}
}
return null;
}
/**
* 根据 userId 分配对应的 view
*
* @param userId
* @return
*/
public TRTCAudioLayout allocAudioCallLayout(String userId) {
if (userId == null) return null;
if (mCount > MAX_USER) {
return null;
}
TRTCLayoutEntity layoutEntity = new TRTCLayoutEntity();
layoutEntity.userId = userId;
layoutEntity.layout = new TRTCAudioLayout(mContext);
layoutEntity.layout.setVisibility(VISIBLE);
mLayoutEntityList.add(layoutEntity);
addView(layoutEntity.layout);
mCount++;
post(new Runnable() {
@Override
public void run() {
makeGirdLayout(true);
}
});
return layoutEntity.layout;
}
/**
* 根据 userId 回收对应的 view
*
* @param userId
*/
public void recyclerAudioCallLayout(String userId) {
if (userId == null) return;
Iterator iterator = mLayoutEntityList.iterator();
while (iterator.hasNext()) {
TRTCLayoutEntity item = (TRTCLayoutEntity) iterator.next();
if (item.userId.equals(userId)) {
removeView(item.layout);
iterator.remove();
mCount--;
break;
}
}
post(new Runnable() {
@Override
public void run() {
makeGirdLayout(true);
}
});
}
/**
* 设置当前音量
*
* @param userId
* @param audioVolume
*/
public void updateAudioVolume(String userId, int audioVolume) {
if (userId == null) return;
for (TRTCLayoutEntity entity : mLayoutEntityList) {
if (entity.layout.getVisibility() == VISIBLE) {
if (userId.equals(entity.userId)) {
entity.layout.setAudioVolume(audioVolume);
}
}
}
}
private TRTCLayoutEntity findEntity(TRTCAudioLayout layout) {
for (TRTCLayoutEntity entity : mLayoutEntityList) {
if (entity.layout == layout) return entity;
}
return null;
}
private TRTCLayoutEntity findEntity(String userId) {
for (TRTCLayoutEntity entity : mLayoutEntityList) {
if (entity.userId.equals(userId)) return entity;
}
return null;
}
/**
* 切换到九宫格布局
*
* @param needUpdate 是否需要更新布局
*/
private void makeGirdLayout(boolean needUpdate) {
if (!mInitParam) {
mGrid1ParamList = Utils.initGrid1Param(getContext(), getWidth(), getHeight());
mGrid2ParamList = Utils.initGrid2Param(getContext(), getWidth(), getHeight());
mGrid3ParamList = Utils.initGrid3Param(getContext(), getWidth(), getHeight());
mGrid4ParamList = Utils.initGrid4Param(getContext(), getWidth(), getHeight());
mGrid9ParamList = Utils.initGrid9Param(getContext(), getWidth(), getHeight());
mInitParam = true;
}
if (needUpdate) {
if (mLayoutEntityList.isEmpty()) {
return;
}
ArrayList<LayoutParams> paramList;
if (mCount <= 1) {
// paramList = mGrid1ParamList;
// TRTCLayoutEntity entity = mLayoutEntityList.get(0);
// entity.layout.setLayoutParams(paramList.get(0));
return;
} else if (mCount == 2) {
paramList = mGrid2ParamList;
} else if (mCount == 3) {
paramList = mGrid3ParamList;
} else if (mCount == 4) {
paramList = mGrid4ParamList;
} else {
paramList = mGrid9ParamList;
}
int layoutIndex = 1;
for (int i = 0; i < mLayoutEntityList.size(); i++) {
TRTCLayoutEntity entity = mLayoutEntityList.get(i);
// 我自己要放在布局的左上角
if (entity.userId.equals(mSelfUserId)) {
entity.layout.setLayoutParams(paramList.get(0));
} else if (layoutIndex < paramList.size()) {
entity.layout.setLayoutParams(paramList.get(layoutIndex++));
}
}
}
}
private static class TRTCLayoutEntity {
public TRTCAudioLayout layout;
public String userId = "";
}
}
package com.tencent.liteav.trtccalling.ui.audiocall.audiolayout;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.AttributeSet;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.tencent.liteav.trtccalling.R;
import com.tencent.liteav.trtccalling.model.util.ImageLoader;
import com.tencent.liteav.trtccalling.ui.common.RoundCornerImageView;
/**
* Module: TRTCGroupAudioLayout
* 语音通话界面中,显示多个用户头像的自定义布局
*/
public class TRTCGroupAudioLayout extends RelativeLayout {
private static final int MIN_AUDIO_VOLUME = 10;
private final Context mContext;
private RoundCornerImageView mImageHead;
private TextView mTextName;
private ImageView mImageAudioInput;
private ImageView mImgLoading;
private boolean mMuteAudio = false; // 静音状态 true : 开启静音
public TRTCGroupAudioLayout(Context context) {
this(context, null);
}
public TRTCGroupAudioLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
inflate(context, R.layout.trtccalling_group_audiocall_item_user_layout, this);
initView();
}
private void initView() {
mImageHead = (RoundCornerImageView) findViewById(R.id.img_head);
mTextName = (TextView) findViewById(R.id.tv_name);
mImageAudioInput = (ImageView) findViewById(R.id.iv_audio_input);
mImgLoading = (ImageView) findViewById(R.id.img_loading);
ImageLoader.loadGifImage(mContext, mImgLoading, R.drawable.trtccalling_loading);
}
public void setAudioVolume(int vol) {
if (mMuteAudio) {
return;
}
mImageAudioInput.setVisibility(vol > MIN_AUDIO_VOLUME ? VISIBLE : GONE);
}
public void muteMic(boolean mute) {
mMuteAudio = mute;
mImageAudioInput.setVisibility(mute ? GONE : VISIBLE);
}
public void setUserName(String userName) {
mTextName.setText(userName);
}
public void setBitmap(Bitmap bitmap) {
mImageHead.setImageBitmap(bitmap);
}
public RoundCornerImageView getImageView() {
return mImageHead;
}
public void startLoading() {
mImgLoading.setVisibility(VISIBLE);
}
public void stopLoading() {
mImgLoading.setVisibility(GONE);
}
}
package com.tencent.liteav.trtccalling.ui.audiocall.audiolayout;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.RelativeLayout;
import java.util.ArrayList;
import java.util.Iterator;
/**
* 通话过程中,用来显示/管理所有用户头像的{@link TRTCAudioLayout}自定义布局
*/
public class TRTCGroupAudioLayoutManager extends RelativeLayout {
private static final String TAG = "TRTCGroupAudioLayoutMng";
public static final int MAX_USER = 9;
private String mSelfUserId;
private Context mContext;
private int mCount = 0;
private boolean mInitParam = false;
private ArrayList<LayoutParams> mGrid1ParamList;
private ArrayList<LayoutParams> mGrid2ParamList;
private ArrayList<LayoutParams> mGrid3ParamList;
private ArrayList<LayoutParams> mGrid4ParamList;
private ArrayList<LayoutParams> mGrid9ParamList;
private ArrayList<TRTCLayoutEntity> mLayoutEntityList;
public TRTCGroupAudioLayoutManager(Context context) {
super(context);
initView(context);
}
public TRTCGroupAudioLayoutManager(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public TRTCGroupAudioLayoutManager(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context) {
Log.i(TAG, "initView: ");
mContext = context;
// 做成正方形
mLayoutEntityList = new ArrayList<TRTCLayoutEntity>();
this.post(new Runnable() {
@Override
public void run() {
makeGirdLayout(true);
}
});
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSize == 0 && heightSize == 0) {
// If there are no constraints on size, let FrameLayout measure
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Now use the smallest of the measured dimensions for both dimensions
final int minSize = Math.min(getMeasuredWidth(), getMeasuredHeight());
setMeasuredDimension(minSize, minSize);
return;
}
final int size;
if (widthSize == 0 || heightSize == 0) {
// If one of the dimensions has no restriction on size, set both dimensions to be the
// on that does
size = Math.max(widthSize, heightSize);
} else {
// Both dimensions have restrictions on size, set both dimensions to be the
// smallest of the two
size = Math.min(widthSize, heightSize);
}
final int newMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
super.onMeasure(newMeasureSpec, newMeasureSpec);
}
public void setMySelfUserId(String userId) {
mSelfUserId = userId;
}
/**
* 根据 userId 找到已经分配的 View
*/
public TRTCGroupAudioLayout findAudioCallLayout(String userId) {
if (userId == null) return null;
for (TRTCLayoutEntity layoutEntity : mLayoutEntityList) {
if (layoutEntity.userId.equals(userId)) {
return layoutEntity.layout;
}
}
return null;
}
/**
* 根据 userId 分配对应的 view
*
* @param userId
* @return
*/
public TRTCGroupAudioLayout allocAudioCallLayout(String userId) {
Log.d(TAG, "allocAudioCallLayout:" + userId);
if (userId == null) return null;
if (mCount > MAX_USER) {
return null;
}
TRTCLayoutEntity layoutEntity = new TRTCLayoutEntity();
layoutEntity.userId = userId;
layoutEntity.layout = new TRTCGroupAudioLayout(mContext);
layoutEntity.layout.setVisibility(VISIBLE);
mLayoutEntityList.add(layoutEntity);
addView(layoutEntity.layout);
mCount++;
post(new Runnable() {
@Override
public void run() {
makeGirdLayout(true);
}
});
return layoutEntity.layout;
}
/**
* 根据 userId 回收对应的 view
*
* @param userId
*/
public void recyclerAudioCallLayout(String userId) {
if (userId == null) return;
Iterator iterator = mLayoutEntityList.iterator();
while (iterator.hasNext()) {
TRTCLayoutEntity item = (TRTCLayoutEntity) iterator.next();
if (item.userId.equals(userId)) {
removeView(item.layout);
iterator.remove();
mCount--;
break;
}
}
post(new Runnable() {
@Override
public void run() {
makeGirdLayout(true);
}
});
}
/**
* 设置当前音量
*
* @param userId
* @param audioVolume
*/
public void updateAudioVolume(String userId, int audioVolume) {
if (userId == null) return;
for (TRTCLayoutEntity entity : mLayoutEntityList) {
if (entity.layout.getVisibility() == VISIBLE) {
if (userId.equals(entity.userId)) {
entity.layout.setAudioVolume(audioVolume);
}
}
}
}
private TRTCLayoutEntity findEntity(TRTCGroupAudioLayout layout) {
for (TRTCLayoutEntity entity : mLayoutEntityList) {
if (entity.layout == layout) return entity;
}
return null;
}
private TRTCLayoutEntity findEntity(String userId) {
for (TRTCLayoutEntity entity : mLayoutEntityList) {
if (entity.userId.equals(userId)) return entity;
}
return null;
}
/**
* 切换到九宫格布局
*
* @param needUpdate 是否需要更新布局
*/
private void makeGirdLayout(boolean needUpdate) {
if (!mInitParam) {
mGrid1ParamList = Utils.initGrid1Param(getContext(), getWidth(), getHeight());
mGrid2ParamList = Utils.initGrid2Param(getContext(), getWidth(), getHeight());
mGrid3ParamList = Utils.initGrid3Param(getContext(), getWidth(), getHeight());
mGrid4ParamList = Utils.initGrid4Param(getContext(), getWidth(), getHeight());
mGrid9ParamList = Utils.initGrid9Param(getContext(), getWidth(), getHeight());
mInitParam = true;
}
if (needUpdate) {
if (mLayoutEntityList.isEmpty()) {
return;
}
ArrayList<LayoutParams> paramList;
if (mCount <= 1) {
paramList = mGrid1ParamList;
TRTCLayoutEntity entity = mLayoutEntityList.get(0);
entity.layout.setLayoutParams(paramList.get(0));
return;
} else if (mCount == 2) {
paramList = mGrid2ParamList;
} else if (mCount == 3) {
paramList = mGrid3ParamList;
} else if (mCount == 4) {
paramList = mGrid4ParamList;
} else {
paramList = mGrid9ParamList;
}
int layoutIndex = TextUtils.isEmpty(mSelfUserId) ? 0 : 1;
for (int i = 0; i < mLayoutEntityList.size(); i++) {
TRTCLayoutEntity entity = mLayoutEntityList.get(i);
// 我自己要放在布局的左上角
if (entity.userId.equals(mSelfUserId)) {
entity.layout.setLayoutParams(paramList.get(0));
} else if (layoutIndex < paramList.size()) {
entity.layout.setLayoutParams(paramList.get(layoutIndex++));
}
}
}
}
private static class TRTCLayoutEntity {
public TRTCGroupAudioLayout layout;
public String userId = "";
}
}
package com.tencent.liteav.trtccalling.ui.audiocall.audiolayout;
import android.content.Context;
import android.widget.RelativeLayout;
import java.util.ArrayList;
public class Utils {
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* 一宫格布局,整体居中
*
* @param context
* @param layoutWidth
* @param layoutHeight
* @return
*/
public static ArrayList<RelativeLayout.LayoutParams> initGrid1Param(Context context, int layoutWidth, int layoutHeight) {
ArrayList<RelativeLayout.LayoutParams> list = new ArrayList<>();
int margin = dip2px(context, 10);
int grid4W = (layoutWidth - margin * 2) / 2;
int grid4H = (layoutHeight - margin * 2) / 2;
// 使用四宫格的大小
RelativeLayout.LayoutParams layoutParams0 = new RelativeLayout.LayoutParams(grid4W, grid4H);
layoutParams0.addRule(RelativeLayout.CENTER_IN_PARENT);
list.add(layoutParams0);
return list;
}
/**
* 二宫格布局,两个layout平分
*
* @param context
* @param layoutWidth
* @param layoutHeight
* @return
*/
public static ArrayList<RelativeLayout.LayoutParams> initGrid2Param(Context context, int layoutWidth, int layoutHeight) {
ArrayList<RelativeLayout.LayoutParams> list = new ArrayList<>();
int margin = dip2px(context, 10);
int grid4W = (layoutWidth - margin * 2) / 2;
int grid4H = (layoutHeight - margin * 2) / 2;
// 使用四宫格的大小
RelativeLayout.LayoutParams layoutParams0 = new RelativeLayout.LayoutParams(grid4W, grid4H);
layoutParams0.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
layoutParams0.addRule(RelativeLayout.CENTER_VERTICAL);
layoutParams0.leftMargin = margin;
RelativeLayout.LayoutParams layoutParams1 = new RelativeLayout.LayoutParams(grid4W, grid4H);
layoutParams1.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
layoutParams1.addRule(RelativeLayout.CENTER_VERTICAL);
layoutParams1.rightMargin = margin;
list.add(layoutParams0);
list.add(layoutParams1);
return list;
}
/**
* 三宫格布局,品字形
*
* @param context
* @param layoutWidth
* @param layoutHeight
* @return
*/
public static ArrayList<RelativeLayout.LayoutParams> initGrid3Param(Context context, int layoutWidth, int layoutHeight) {
int margin = dip2px(context, 10);
ArrayList<RelativeLayout.LayoutParams> list = new ArrayList<>();
int grid4W = (layoutWidth - margin * 2) / 2;
int grid4H = (layoutHeight - margin * 2) / 2;
RelativeLayout.LayoutParams layoutParams0 = new RelativeLayout.LayoutParams(grid4W, grid4H);
layoutParams0.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
layoutParams0.addRule(RelativeLayout.ALIGN_PARENT_TOP);
layoutParams0.topMargin = margin;
layoutParams0.leftMargin = margin;
RelativeLayout.LayoutParams layoutParams1 = new RelativeLayout.LayoutParams(grid4W, grid4H);
layoutParams1.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
layoutParams1.addRule(RelativeLayout.ALIGN_PARENT_TOP);
layoutParams1.topMargin = margin;
layoutParams1.rightMargin = margin;
RelativeLayout.LayoutParams layoutParams2 = new RelativeLayout.LayoutParams(grid4W, grid4H);
layoutParams2.addRule(RelativeLayout.CENTER_HORIZONTAL);
layoutParams2.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
layoutParams2.leftMargin = margin;
layoutParams2.bottomMargin = margin;
list.add(layoutParams0);
list.add(layoutParams1);
list.add(layoutParams2);
return list;
}
/**
* 四宫格布局参数
*
* @param context
* @param layoutWidth
* @param layoutHeight
* @return
*/
public static ArrayList<RelativeLayout.LayoutParams> initGrid4Param(Context context, int layoutWidth, int layoutHeight) {
int margin = dip2px(context, 10);
ArrayList<RelativeLayout.LayoutParams> list = new ArrayList<>();
int grid4W = (layoutWidth - margin * 2) / 2;
int grid4H = (layoutHeight - margin * 2) / 2;
RelativeLayout.LayoutParams layoutParams0 = new RelativeLayout.LayoutParams(grid4W, grid4H);
layoutParams0.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
layoutParams0.addRule(RelativeLayout.ALIGN_PARENT_TOP);
layoutParams0.topMargin = margin;
layoutParams0.leftMargin = margin;
RelativeLayout.LayoutParams layoutParams1 = new RelativeLayout.LayoutParams(grid4W, grid4H);
layoutParams1.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
layoutParams1.addRule(RelativeLayout.ALIGN_PARENT_TOP);
layoutParams1.topMargin = margin;
layoutParams1.rightMargin = margin;
RelativeLayout.LayoutParams layoutParams2 = new RelativeLayout.LayoutParams(grid4W, grid4H);
layoutParams2.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
layoutParams2.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
layoutParams2.bottomMargin = margin;
layoutParams2.leftMargin = margin;
RelativeLayout.LayoutParams layoutParams3 = new RelativeLayout.LayoutParams(grid4W, grid4H);
layoutParams3.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
layoutParams3.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
layoutParams3.bottomMargin = margin;
layoutParams3.rightMargin = margin;
list.add(layoutParams0);
list.add(layoutParams1);
list.add(layoutParams2);
list.add(layoutParams3);
return list;
}
/**
* 九宫格布局参数
*
* @param context
* @param layoutWidth
* @param layoutHeight
* @return
*/
public static ArrayList<RelativeLayout.LayoutParams> initGrid9Param(Context context, int layoutWidth, int layoutHeight) {
int margin = dip2px(context, 10);
ArrayList<RelativeLayout.LayoutParams> list = new ArrayList<>();
int grid9W = (layoutWidth - margin * 2) / 3;
int grid9H = (layoutHeight - margin * 2) / 3;
RelativeLayout.LayoutParams layoutParams0 = new RelativeLayout.LayoutParams(grid9W, grid9H);
layoutParams0.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
layoutParams0.addRule(RelativeLayout.ALIGN_PARENT_TOP);
layoutParams0.topMargin = margin;
layoutParams0.leftMargin = margin;
RelativeLayout.LayoutParams layoutParams1 = new RelativeLayout.LayoutParams(grid9W, grid9H);
layoutParams1.addRule(RelativeLayout.CENTER_HORIZONTAL);
layoutParams1.addRule(RelativeLayout.ALIGN_PARENT_TOP);
layoutParams1.topMargin = margin;
RelativeLayout.LayoutParams layoutParams2 = new RelativeLayout.LayoutParams(grid9W, grid9H);
layoutParams2.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
layoutParams2.addRule(RelativeLayout.ALIGN_PARENT_TOP);
layoutParams2.topMargin = margin;
layoutParams2.rightMargin = margin;
RelativeLayout.LayoutParams layoutParams3 = new RelativeLayout.LayoutParams(grid9W, grid9H);
layoutParams3.addRule(RelativeLayout.ALIGN_PARENT_TOP);
layoutParams3.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
layoutParams3.leftMargin = margin;
layoutParams3.topMargin = margin + grid9H;
RelativeLayout.LayoutParams layoutParams4 = new RelativeLayout.LayoutParams(grid9W, grid9H);
layoutParams4.addRule(RelativeLayout.CENTER_HORIZONTAL);
layoutParams4.topMargin = margin + grid9H;
RelativeLayout.LayoutParams layoutParams5 = new RelativeLayout.LayoutParams(grid9W, grid9H);
layoutParams5.addRule(RelativeLayout.ALIGN_PARENT_TOP);
layoutParams5.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
layoutParams5.topMargin = margin + grid9H;
layoutParams5.rightMargin = margin;
RelativeLayout.LayoutParams layoutParams6 = new RelativeLayout.LayoutParams(grid9W, grid9H);
layoutParams6.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
layoutParams6.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
layoutParams6.bottomMargin = margin;
layoutParams6.leftMargin = margin;
RelativeLayout.LayoutParams layoutParams7 = new RelativeLayout.LayoutParams(grid9W, grid9H);
layoutParams7.addRule(RelativeLayout.CENTER_HORIZONTAL);
layoutParams7.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
layoutParams7.bottomMargin = margin;
RelativeLayout.LayoutParams layoutParams8 = new RelativeLayout.LayoutParams(grid9W, grid9H);
layoutParams8.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
layoutParams8.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
layoutParams8.bottomMargin = margin;
layoutParams8.rightMargin = margin;
list.add(layoutParams0);
list.add(layoutParams1);
list.add(layoutParams2);
list.add(layoutParams3);
list.add(layoutParams4);
list.add(layoutParams5);
list.add(layoutParams6);
list.add(layoutParams7);
list.add(layoutParams8);
return list;
}
}
package com.tencent.liteav.trtccalling.ui.base;
public class Status {
//悬浮窗是否开启
public static boolean mIsShowFloatWindow = false;
//悬浮窗当前显示的userId
//未接通前等于自己,接通后等于对方
public static String mCurFloatUserId = "";
//悬浮窗需要显示的通话开始时间
public static int mBeginTime;
//悬浮窗通话状态
public static CALL_STATUS mCallStatus = CALL_STATUS.NONE;
public enum CALL_STATUS {
NONE, // 无状态
WAITING, // 正在等待接听
ACCEPT // 已接听
}
}
package com.tencent.liteav.trtccalling.ui.base;
import com.tencent.liteav.trtccalling.ui.videocall.videolayout.TRTCVideoLayout;
public class TRTCLayoutEntity {
public TRTCVideoLayout layout;
public String userId = "";
}
package com.tencent.liteav.trtccalling.ui.base;
import android.content.Context;
import android.view.View;
import com.tencent.liteav.trtccalling.ui.videocall.videolayout.TRTCVideoLayout;
import java.util.Iterator;
import java.util.LinkedList;
public class VideoLayoutFactory {
private final Context mContext;
public LinkedList<TRTCLayoutEntity> mLayoutEntityList;
public VideoLayoutFactory(Context context) {
mContext = context;
mLayoutEntityList = new LinkedList<>();
}
/**
* 根据 userId 找到已经分配的 View
*
* @param userId
* @return
*/
public TRTCVideoLayout findUserLayout(String userId) {
if (userId == null) return null;
for (TRTCLayoutEntity layoutEntity : mLayoutEntityList) {
if (layoutEntity.userId.equals(userId)) {
return layoutEntity.layout;
}
}
return null;
}
/**
* 根据 userId 分配对应的 view
*
* @param userId
* @return
*/
public TRTCVideoLayout allocUserLayout(String userId, TRTCVideoLayout layout) {
if (userId == null) return null;
TRTCLayoutEntity layoutEntity = new TRTCLayoutEntity();
layoutEntity.userId = userId;
layoutEntity.layout = layout;
layoutEntity.layout.setVisibility(View.VISIBLE);
mLayoutEntityList.add(layoutEntity);
return layoutEntity.layout;
}
/**
* 根据 userId 和 视频类型,回收对应的 view
*
* @param userId
*/
public void recyclerCloudViewView(String userId) {
if (userId == null) return;
Iterator iterator = mLayoutEntityList.iterator();
while (iterator.hasNext()) {
TRTCLayoutEntity item = (TRTCLayoutEntity) iterator.next();
if (item.userId.equals(userId)) {
iterator.remove();
break;
}
}
}
}
package com.tencent.liteav.trtccalling.ui.common;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
import androidx.appcompat.widget.AppCompatImageView;
import com.tencent.liteav.trtccalling.R;
public class RoundCornerImageView extends AppCompatImageView {
private float mWidth, mHeight;
private int mDefaultRadius = 0;
private int mRadius;
private int mLeftTopRadius;
private int mRightTopRadius;
private int mRightBottomRadius;
private int mLeftBottomRadius;
public RoundCornerImageView(Context context) {
this(context, null);
}
public RoundCornerImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundCornerImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
// 读取配置
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TRTCCallingRoundCornerImageView);
mRadius = array.getDimensionPixelOffset(R.styleable.TRTCCallingRoundCornerImageView_trtc_radius, mDefaultRadius);
mLeftTopRadius = array.getDimensionPixelOffset(R.styleable.TRTCCallingRoundCornerImageView_left_top_radius, mDefaultRadius);
mRightTopRadius = array.getDimensionPixelOffset(R.styleable.TRTCCallingRoundCornerImageView_right_top_radius, mDefaultRadius);
mRightBottomRadius = array.getDimensionPixelOffset(R.styleable.TRTCCallingRoundCornerImageView_right_bottom_radius, mDefaultRadius);
mLeftBottomRadius = array.getDimensionPixelOffset(R.styleable.TRTCCallingRoundCornerImageView_left_bottom_radius, mDefaultRadius);
//如果四个角的值没有设置,那么就使用通用的radius的值。
if (mDefaultRadius == mLeftTopRadius) {
mLeftTopRadius = mRadius;
}
if (mDefaultRadius == mRightTopRadius) {
mRightTopRadius = mRadius;
}
if (mDefaultRadius == mRightBottomRadius) {
mRightBottomRadius = mRadius;
}
if (mDefaultRadius == mLeftBottomRadius) {
mLeftBottomRadius = mRadius;
}
array.recycle();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mWidth = getWidth();
mHeight = getHeight();
}
@Override
protected void onDraw(Canvas canvas) {
// 只有图片的宽高大于设置的圆角距离的时候才进行裁剪
int maxLeft = Math.max(mLeftTopRadius, mLeftBottomRadius);
int maxRight = Math.max(mRightTopRadius, mRightBottomRadius);
int minWidth = maxLeft + maxRight;
int maxTop = Math.max(mLeftTopRadius, mRightTopRadius);
int maxBottom = Math.max(mLeftBottomRadius, mRightBottomRadius);
int minHeight = maxTop + maxBottom;
if (mWidth >= minWidth && mHeight > minHeight) {
Path path = new Path();
//四个角:右上,右下,左下,左上
path.moveTo(mLeftTopRadius, 0);
path.lineTo(mWidth - mRightTopRadius, 0);
path.quadTo(mWidth, 0, mWidth, mRightTopRadius);
path.lineTo(mWidth, mHeight - mRightBottomRadius);
path.quadTo(mWidth, mHeight, mWidth - mRightBottomRadius, mHeight);
path.lineTo(mLeftBottomRadius, mHeight);
path.quadTo(0, mHeight, 0, mHeight - mLeftBottomRadius);
path.lineTo(0, mLeftTopRadius);
path.quadTo(0, 0, mLeftTopRadius, 0);
canvas.clipPath(path);
}
super.onDraw(canvas);
}
}
\ No newline at end of file
package com.tencent.liteav.trtccalling.ui.common;
import android.content.Context;
import android.os.PowerManager;
import android.util.TypedValue;
import android.view.Window;
import android.view.WindowManager;
public class Utils {
public static int dp2px(Context context, float dpVal) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dpVal, context.getResources().getDisplayMetrics());
}
//锁屏下可直接拉起界面,且通话界面不黑屏
public static void setScreenLockParams(Window window) {
if (null == window) {
return;
}
PowerManager powerManager = (PowerManager) window.getContext().getSystemService(Context.POWER_SERVICE);
boolean isScreenOn = powerManager.isScreenOn();
if (isScreenOn) {
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} else {
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
}
}
}
package com.tencent.liteav.trtccalling.ui.floatwindow;
import android.animation.ValueAnimator;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import com.tencent.liteav.trtccalling.R;
import com.tencent.liteav.trtccalling.model.impl.base.TRTCLogger;
import com.tencent.liteav.trtccalling.ui.base.BaseTUICallView;
import com.tencent.liteav.trtccalling.ui.base.Status;
/**
* TUICalling组件悬浮窗服务
* 组件通过home键退到后台,或者通过左上角按钮退到前一个界面时,拉起悬浮窗;
* 点击悬浮窗可重新进入界面,且悬浮窗消失;
* 直接点击桌面icon,也可重新进入界面,悬浮窗消失.
*/
public class FloatWindowService extends Service {
private static final String TAG = "FloatWindowService";
private static Intent mStartIntent;
private static BaseTUICallView mCallView;
private static Context mContext;
private WindowManager mWindowManager;
private WindowManager.LayoutParams mWindowLayoutParams;
private int mScreenWidth; //屏幕宽度
private int mWidth; //悬浮窗宽度
public static void startFloatService(Context context, BaseTUICallView callView) {
TRTCLogger.i(TAG, "startFloatService");
mContext = context;
mCallView = callView;
mStartIntent = new Intent(context, FloatWindowService.class);
context.startService(mStartIntent);
}
public static void stopService(Context context) {
TRTCLogger.i(TAG, "stopService: startIntent = " + mStartIntent);
if (null != mStartIntent) {
context.stopService(mStartIntent);
}
}
@Override
public void onCreate() {
super.onCreate();
initWindow();
}
@Override
public IBinder onBind(Intent intent) {
return new FloatBinder();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Status.mIsShowFloatWindow = false;
TRTCLogger.i(TAG, "onDestroy: mCallView = " + mCallView);
if (null != mCallView) {
// 移除悬浮窗口
mWindowManager.removeView(mCallView);
mCallView = null;
}
}
/**
* 设置悬浮窗基本参数(位置、宽高等)
*/
private void initWindow() {
mWindowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
//设置好悬浮窗的参数
mWindowLayoutParams = getParams();
//屏幕宽度
mScreenWidth = mWindowManager.getDefaultDisplay().getWidth();
// 添加悬浮窗的视图
TRTCLogger.i(TAG, "initWindow: mCallView = " + mCallView);
if (null != mCallView) {
mCallView.setBackgroundResource(R.drawable.trtccalling_bg_floatwindow_left);
mWindowManager.addView(mCallView, mWindowLayoutParams);
mCallView.setOnTouchListener(new FloatingListener());
}
}
private WindowManager.LayoutParams getParams() {
mWindowLayoutParams = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mWindowLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
mWindowLayoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}
mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
// 悬浮窗默认显示以左上角为起始坐标
mWindowLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
//悬浮窗的开始位置,设置从左上角开始,所以屏幕左上角是x=0,y=0.
mWindowLayoutParams.x = 0;
mWindowLayoutParams.y = mWindowManager.getDefaultDisplay().getHeight() / 2;
//设置悬浮窗宽高
mWindowLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
mWindowLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
mWindowLayoutParams.format = PixelFormat.TRANSPARENT;
return mWindowLayoutParams;
}
//======================================悬浮窗Touch和贴边事件=============================//
//开始触控的坐标,移动时的坐标(相对于屏幕左上角的坐标)
private int mTouchStartX;
private int mTouchStartY;
private int mTouchCurrentX;
private int mTouchCurrentY;
//开始触控的坐标和结束时的坐标(相对于屏幕左上角的坐标)
private int mStartX;
private int mStartY;
private int mStopX;
private int mStopY;
//标记悬浮窗是否移动,防止移动后松手触发了点击事件
private boolean mIsMove;
public class FloatBinder extends Binder {
public FloatWindowService getService() {
return FloatWindowService.this;
}
}
private class FloatingListener implements View.OnTouchListener {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mIsMove = false;
mTouchStartX = (int) event.getRawX(); //触摸点相对屏幕显示器左上角的坐标
mTouchStartY = (int) event.getRawY();
//悬浮窗不是全屏的,因此不能用getX()标记开始点,getX()是触摸点相对自身左上角的坐标
mStartX = (int) event.getRawX();
mStartY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
mTouchCurrentX = (int) event.getRawX();
mTouchCurrentY = (int) event.getRawY();
mWindowLayoutParams.x += mTouchCurrentX - mTouchStartX;
mWindowLayoutParams.y += mTouchCurrentY - mTouchStartY;
if (null != mCallView) {
mWindowManager.updateViewLayout(mCallView, mWindowLayoutParams);
}
mTouchStartX = mTouchCurrentX;
mTouchStartY = mTouchCurrentY;
case MotionEvent.ACTION_UP:
mStopX = (int) event.getRawX();
mStopY = (int) event.getRawY();
if (Math.abs(mStartX - mStopX) >= 5 || Math.abs(mStartY - mStopY) >= 5) {
mIsMove = true;
if (null != mCallView) {
mWidth = mCallView.getWidth();
//超出一半屏幕右移
if (mTouchCurrentX > (mScreenWidth / 2)) {
startScroll(mStopX, mScreenWidth - mWidth, false);
} else {
startScroll(mStopX, 0, true);
}
}
}
break;
default:
break;
}
//移动事件拦截
return mIsMove;
}
}
//悬浮窗贴边动画
private void startScroll(int start, int end, boolean isLeft) {
mWidth = mCallView.getWidth();
ValueAnimator valueAnimator = ValueAnimator.ofFloat(start, end).setDuration(300);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (isLeft) {
mWindowLayoutParams.x = (int) (start * (1 - animation.getAnimatedFraction()));
mCallView.setBackgroundResource(R.drawable.trtccalling_bg_floatwindow_left);
} else {
mWindowLayoutParams.x = (int) (start + (mScreenWidth - start - mWidth) * animation.getAnimatedFraction());
mCallView.setBackgroundResource(R.drawable.trtccalling_bg_floatwindow_right);
}
calculateHeight();
mWindowManager.updateViewLayout(mCallView, mWindowLayoutParams);
}
});
valueAnimator.start();
}
//计算高度,防止悬浮窗上下越界
private void calculateHeight() {
int height = mCallView.getHeight();
int screenHeight = mWindowManager.getDefaultDisplay().getHeight();
//获取系统状态栏的高度
int resourceId = mContext.getResources().getIdentifier("status_bar_height",
"dimen", "android");
int statusBarHeight = mContext.getResources().getDimensionPixelSize(resourceId);
if (mWindowLayoutParams.y < 0) {
mWindowLayoutParams.y = 0;
} else if (mWindowLayoutParams.y > (screenHeight - height - statusBarHeight)) {
mWindowLayoutParams.y = screenHeight - height - statusBarHeight;
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment