Commit 12ea5d71 by konghaorui

私聊模块 迁移

parent b78de735

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

...@@ -199,6 +199,7 @@ dependencies { ...@@ -199,6 +199,7 @@ dependencies {
implementation project(':m-muse') implementation project(':m-muse')
implementation project(':m-im')
// api rootProject.ext.dependencies["ydl-m-fm-module-ydl"] // api rootProject.ext.dependencies["ydl-m-fm-module-ydl"]
......
...@@ -17,11 +17,13 @@ kapt { ...@@ -17,11 +17,13 @@ kapt {
} }
android { android {
compileSdkVersion 28 compileSdkVersion rootProject.ext.android["compileSdkVersion"]
buildToolsVersion rootProject.ext.android["buildToolsVersion"]
defaultConfig { defaultConfig {
minSdkVersion 14 minSdkVersion rootProject.ext.android["minSdkVersion"]
targetSdkVersion 28 targetSdkVersion rootProject.ext.android["targetSdkVersion"]
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
...@@ -35,6 +37,9 @@ android { ...@@ -35,6 +37,9 @@ android {
flavorDimensions "versionCode"//Flavor 维度信息 flavorDimensions "versionCode"//Flavor 维度信息
ndk {
abiFilters "armeabi-v7a"
}
} }
buildTypes { buildTypes {
...@@ -45,7 +50,7 @@ android { ...@@ -45,7 +50,7 @@ android {
} }
//前缀的名字 //前缀的名字
resourcePrefix "im_" //resourcePrefix "im_"
//Flavor 信息 //Flavor 信息
publishNonDefault true publishNonDefault true
...@@ -58,7 +63,9 @@ android { ...@@ -58,7 +63,9 @@ android {
main { main {
manifest.srcFile 'src/main/AndroidManifest.xml' manifest.srcFile 'src/main/AndroidManifest.xml'
res.srcDirs = [ res.srcDirs = [
'src/main/res' 'src/main/res',
'src/main/res_avchat',
'src/main/res_uikit'
] ]
} }
} }
...@@ -75,8 +82,12 @@ dependencies { ...@@ -75,8 +82,12 @@ dependencies {
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
kapt 'com.alibaba:arouter-compiler:1.2.2' kapt 'com.alibaba:arouter-compiler:1.2.2'
implementation "org.jetbrains.kotlin:kotlin-script-runtime:1.3.41" api 'com.ydl:nim-base:1.0.1'
api 'com.netease.nimlib:basesdk:6.4.0'
api 'com.netease.nimlib:avchat:6.4.0'
api 'com.netease.nimlib:nrtc:6.4.0'
api rootProject.ext.dependencies["ydl-user-router"] api rootProject.ext.dependencies["ydl-user-router"]
if (rootProject.ext.dev_mode){ if (rootProject.ext.dev_mode){
...@@ -84,11 +95,8 @@ dependencies { ...@@ -84,11 +95,8 @@ dependencies {
api project(':ydl-webview') api project(':ydl-webview')
api project(':ydl-platform') api project(':ydl-platform')
implementation modularPublication('com.ydl:m-user-api') implementation modularPublication('com.ydl:m-user-api')
implementation modularPublication('com.ydl:m-audioim-api')
}else { }else {
//发布时使用 //发布时使用
compileOnly rootProject.ext.dependencies["ydl-m-user-api"]
compileOnly rootProject.ext.dependencies["ydl-m-audioim-api"]
api rootProject.ext.dependencies["ydl-webview"] api rootProject.ext.dependencies["ydl-webview"]
api(rootProject.ext.dependencies["ydl-platform"]) { api(rootProject.ext.dependencies["ydl-platform"]) {
transitive = true transitive = true
......
package com.yidianling.avchatkit;
import android.app.Notification;
import android.content.Context;
import android.util.Log;
import android.util.SparseArray;
import com.netease.nimlib.sdk.Observer;
import com.netease.nimlib.sdk.avchat.AVChatManager;
import com.netease.nimlib.sdk.avchat.constant.AVChatControlCommand;
import com.netease.nimlib.sdk.avchat.model.AVChatData;
import com.yidianling.avchatkit.activity.AVChatActivity;
import com.yidianling.avchatkit.common.log.ILogUtil;
import com.yidianling.avchatkit.common.log.LogUtil;
import com.yidianling.avchatkit.config.AVChatOptions;
import com.yidianling.avchatkit.model.ITeamDataProvider;
import com.yidianling.avchatkit.model.IUserInfoProvider;
import com.yidianling.avchatkit.receiver.PhoneCallStateObserver;
import com.yidianling.avchatkit.teamavchat.activity.TeamAVChatActivity;
import java.util.ArrayList;
/**
* 云信音视频组件定制化入口
* Created by winnie on 2017/12/6.
*/
public class AVChatKit {
private static final String TAG = AVChatKit.class.getSimpleName();
private static Context context;
private static String account;
private static boolean mainTaskLaunching;
private static AVChatOptions avChatOptions;
private static IUserInfoProvider userInfoProvider;
private static ITeamDataProvider teamDataProvider;
private static ILogUtil iLogUtil;
private static SparseArray<Notification> notifications = new SparseArray<>();
public static void init(AVChatOptions avChatOptions) {
AVChatKit.avChatOptions = avChatOptions;
registerAVChatIncomingCallObserver(true);
}
public static void setContext(Context context) {
AVChatKit.context = context;
}
public static Context getContext() {
return context;
}
public static String getAccount() {
return account;
}
public static void setAccount(String account) {
AVChatKit.account = account;
}
public static void setMainTaskLaunching(boolean mainTaskLaunching) {
AVChatKit.mainTaskLaunching = mainTaskLaunching;
}
public static boolean isMainTaskLaunching() {
return mainTaskLaunching;
}
/**
* 获取通知栏提醒数组
*/
public static SparseArray<Notification> getNotifications() {
return notifications;
}
/**
* 获取音视频初始化配置
* @return AVChatOptions
*/
public static AVChatOptions getAvChatOptions() {
return avChatOptions;
}
/**
* 设置用户相关资料提供者
* @param userInfoProvider 用户相关资料提供者
*/
public static void setUserInfoProvider(IUserInfoProvider userInfoProvider) {
AVChatKit.userInfoProvider = userInfoProvider;
}
/**
* 获取用户相关资料提供者
* @return IUserInfoProvider
*/
public static IUserInfoProvider getUserInfoProvider() {
return userInfoProvider;
}
/**
* 获取日志系统接口
* @return ILogUtil
*/
public static ILogUtil getiLogUtil() {
return iLogUtil;
}
/**
* 设置日志系统接口
* @param iLogUtil 日志系统接口
*/
public static void setiLogUtil(ILogUtil iLogUtil) {
AVChatKit.iLogUtil = iLogUtil;
}
/**
* 设置群组数据提供者
* @param teamDataProvider 群组数据提供者
*/
public static void setTeamDataProvider(ITeamDataProvider teamDataProvider) {
AVChatKit.teamDataProvider = teamDataProvider;
}
/**
* 获取群组数据提供者
* @return ITeamDataProvider
*/
public static ITeamDataProvider getTeamDataProvider() {
return teamDataProvider;
}
/**
* 发起音视频通话呼叫
* @param context 上下文
* @param account 被叫方账号
* @param displayName 被叫方显示名称
* @param callType 音视频呼叫类型
* @param source 发起呼叫的来源,参考AVChatActivityEx.FROM_INTERNAL/FROM_BROADCASTRECEIVER
*/
public static void outgoingCall(Context context, String account, String displayName, int callType, int source) {
AVChatActivity.outgoingCall(context, account, displayName, callType, source);
}
/**
* 发起群组音视频通话呼叫
* @param context 上下文
* @param receivedCall 是否是接收到的来电
* @param teamId team id
* @param roomId 音视频通话room id
* @param accounts 音视频通话账号集合
* @param teamName 群组名称
*/
public static void outgoingTeamCall(Context context, boolean receivedCall, String teamId, String roomId, ArrayList<String> accounts, String teamName) {
TeamAVChatActivity.startActivity(context, receivedCall, teamId, roomId, accounts, teamName);
}
/**
* 注册音视频来电观察者
* @param register 注册或注销
*/
private static void registerAVChatIncomingCallObserver(boolean register) {
AVChatManager.getInstance().observeIncomingCall(inComingCallObserver, register);
}
private static Observer<AVChatData> inComingCallObserver = new Observer<AVChatData>() {
@Override
public void onEvent(final AVChatData data) {
String extra = data.getExtra();
Log.e("Extra", "Extra Message->" + extra);
if (PhoneCallStateObserver.getInstance().getPhoneCallState() != PhoneCallStateObserver.PhoneCallStateEnum.IDLE
|| AVChatProfile.getInstance().isAVChatting()
|| TeamAVChatProfile.sharedInstance().isTeamAVChatting()
|| AVChatManager.getInstance().getCurrentChatId() != 0) {
LogUtil.i(TAG, "reject incoming call data =" + data.toString() + " as local phone is not idle");
AVChatManager.getInstance().sendControlCommand(data.getChatId(), AVChatControlCommand.BUSY, null);
return;
}
// 有网络来电打开AVChatActivity
AVChatProfile.getInstance().setAVChatting(true);
AVChatProfile.getInstance().launchActivity(data, userInfoProvider.getUserDisplayName(data.getAccount()), AVChatActivity.FROM_BROADCASTRECEIVER);
}
};
}
package com.yidianling.avchatkit;
import android.os.Handler;
import com.netease.nimlib.sdk.avchat.model.AVChatData;
import com.yidianling.avchatkit.activity.AVChatActivity;
import com.yidianling.avchatkit.common.Handlers;
/**
* Created by huangjun on 2015/5/12.
*/
public class AVChatProfile {
private final String TAG = "AVChatProfile";
private boolean isAVChatting = false; // 是否正在音视频通话
public static AVChatProfile getInstance() {
return InstanceHolder.instance;
}
public boolean isAVChatting() {
return isAVChatting;
}
public void setAVChatting(boolean chating) {
isAVChatting = chating;
}
private static class InstanceHolder {
public final static AVChatProfile instance = new AVChatProfile();
}
public void launchActivity(final AVChatData data, final String displayName, final int source) {
Runnable runnable = new Runnable() {
@Override
public void run() {
// 启动,如果 task正在启动,则稍等一下
if (!AVChatKit.isMainTaskLaunching()) {
launchActivityTimeout();
AVChatActivity.incomingCall(AVChatKit.getContext(), data, displayName, source);
} else {
launchActivity(data, displayName, source);
}
}
};
Handlers.sharedHandler(AVChatKit.getContext()).postDelayed(runnable, 200);
}
public void activityLaunched() {
Handler handler = Handlers.sharedHandler(AVChatKit.getContext());
handler.removeCallbacks(launchTimeout);
}
// 有些设备(比如OPPO、VIVO)默认不允许从后台broadcast receiver启动activity
// 增加启动activity超时机制
private void launchActivityTimeout() {
Handler handler = Handlers.sharedHandler(AVChatKit.getContext());
handler.removeCallbacks(launchTimeout);
handler.postDelayed(launchTimeout, 3000);
}
private Runnable launchTimeout = new Runnable() {
@Override
public void run() {
// 如果未成功启动,就恢复av chatting -> false
setAVChatting(false);
}
};
}
\ No newline at end of file
package com.yidianling.avchatkit;
import android.widget.Toast;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.netease.nimlib.sdk.NIMClient;
import com.netease.nimlib.sdk.Observer;
import com.netease.nimlib.sdk.auth.AuthServiceObserver;
import com.netease.nimlib.sdk.auth.constant.LoginSyncStatus;
import com.netease.nimlib.sdk.msg.MsgServiceObserve;
import com.netease.nimlib.sdk.msg.model.CustomNotification;
import com.yidianling.avchatkit.common.Handlers;
import com.yidianling.avchatkit.common.log.LogUtil;
import com.yidianling.avchatkit.common.util.TimeUtil;
import com.yidianling.avchatkit.teamavchat.activity.TeamAVChatActivity;
import java.util.ArrayList;
import java.util.List;
/**
* Created by hzchenkang on 2017/5/5.
*/
public class TeamAVChatProfile {
private static final String KEY_ID = "id";
private static final String KEY_MEMBER = "members";
private static final String KEY_TID = "teamId";
private static final String KEY_RID = "room";
private static final String KEY_TNAME = "teamName";
private static final long OFFLINE_EXPIRY = 45 * 1000;
private static final int ID = 3;
private boolean isTeamAVChatting = false;
private boolean isSyncComplete = true; // 未开始也算同步完成,可能存在不启动同步的情况
public String buildContent(String roomName, String teamID, List<String> accounts, String teamName) {
JSONObject json = new JSONObject();
json.put(KEY_ID, ID);
JSONArray array = new JSONArray();
array.add(AVChatKit.getAccount());
for (String account : accounts) {
array.add(account);
}
json.put(KEY_MEMBER, array);
json.put(KEY_TID, teamID);
json.put(KEY_RID, roomName);
json.put(KEY_TNAME, teamName);
return json.toString();
}
private JSONObject parseContentJson(CustomNotification notification) {
if (notification != null) {
String content = notification.getContent();
return JSONObject.parseObject(content);
}
return null;
}
private boolean isTeamAVChatInvite(JSONObject json) {
if (json != null) {
int id = json.getInteger(KEY_ID);
return id == ID;
}
return false;
}
/**
* 监听自定义通知消息,id = 3 是群视频邀请
*
* @param register
*/
private Observer<CustomNotification> customNotificationObserver = new Observer<CustomNotification>() {
@Override
public void onEvent(CustomNotification customNotification) {
try {
JSONObject jsonObject = parseContentJson(customNotification);
// 收到群视频邀请
if (isTeamAVChatInvite(jsonObject)) {
final String roomName = jsonObject.getString(KEY_RID);
final String teamId = jsonObject.getString(KEY_TID);
JSONArray accountArray = jsonObject.getJSONArray(KEY_MEMBER);
final ArrayList<String> accounts = new ArrayList<>();
final String teamName = jsonObject.getString(KEY_TNAME);
if (accountArray != null) {
for (Object o : accountArray) {
accounts.add((String) o);
}
}
// 接收到群视频邀请,启动来点界面
LogUtil.ui("receive team video chat notification " + teamId + " room " + roomName);
if (isTeamAVChatting || AVChatProfile.getInstance().isAVChatting()) {
LogUtil.ui("cancel launch team av chat isTeamAVChatting = " + isTeamAVChatting);
Toast.makeText(AVChatKit.getContext(), "正在进行视频通话", Toast.LENGTH_SHORT).show();
return;
}
LogUtil.ui("isSyncComplete = " + isSyncComplete);
if (isSyncComplete || !checkOfflineOutTime(customNotification)) {
isTeamAVChatting = true;
launchActivity(teamId, roomName, accounts, teamName);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
private void launchActivity(final String teamId, final String roomName, final ArrayList<String> accounts, final String teamName) {
Runnable r = new Runnable() {
@Override
public void run() {
// 欢迎界面正在运行,则等MainActivity启动之后再启动,否则直接启动 TeamAVChatActivity
if (!AVChatKit.isMainTaskLaunching()) {
TeamAVChatActivity.startActivity(AVChatKit.getContext(), true, teamId, roomName, accounts, teamName);
} else {
LogUtil.ui("launch TeamAVChatActivity delay for WelComeActivity is Launching");
launchActivity(teamId, roomName, accounts, teamName);
}
}
};
Handlers.sharedHandler(AVChatKit.getContext()).postDelayed(r, 200);
}
private Observer<LoginSyncStatus> loginSyncStatusObserver = new Observer<LoginSyncStatus>() {
@Override
public void onEvent(LoginSyncStatus loginSyncStatus) {
isSyncComplete = (loginSyncStatus == LoginSyncStatus.SYNC_COMPLETED ||
loginSyncStatus == LoginSyncStatus.NO_BEGIN);
}
};
public boolean checkOfflineOutTime(CustomNotification notification) {
// 时间差在45s内,考虑本地时间误差,条件适当放宽
long time = TimeUtil.currentTimeMillis() - notification.getTime();
LogUtil.ui("rev offline team AVChat request time = " + time);
return time > OFFLINE_EXPIRY || time < -OFFLINE_EXPIRY;
}
public void setTeamAVChatting(boolean teamAVChatting) {
isTeamAVChatting = teamAVChatting;
}
public boolean isTeamAVChatting() {
return isTeamAVChatting;
}
public void registerObserver(boolean register) {
NIMClient.getService(AuthServiceObserver.class).observeLoginSyncDataStatus(loginSyncStatusObserver, register);
NIMClient.getService(MsgServiceObserve.class).observeCustomNotification(customNotificationObserver, register);
}
public static TeamAVChatProfile sharedInstance() {
return InstanceHolder.teamAVChatProfile;
}
private static class InstanceHolder {
private final static TeamAVChatProfile teamAVChatProfile = new TeamAVChatProfile();
}
}
package com.yidianling.avchatkit.common;
import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
import android.text.TextUtils;
import java.util.HashMap;
public final class Handlers {
public static final String DEFAULT_TAG = "Default";
private static Handlers instance;
public static synchronized Handlers sharedInstance() {
if (instance == null) {
instance = new Handlers();
}
return instance;
}
private static Handler sharedHandler;
/**
* get shared handler for main looper
*
* @param context
* @return
*/
public static final Handler sharedHandler(Context context) {
/**
* duplicate handlers !!! i don't care
*/
if (sharedHandler == null) {
sharedHandler = new Handler(context.getMainLooper());
}
return sharedHandler;
}
/**
* get new handler for main looper
*
* @param context
* @return
*/
public static final Handler newHandler(Context context) {
return new Handler(context.getMainLooper());
}
private Handlers() {
}
/**
* get new handler for a background default looper
*
* @return
*/
public final Handler newHandler() {
return newHandler(DEFAULT_TAG);
}
/**
* get new handler for a background stand alone looper identified by tag
*
* @param tag
* @return
*/
public final Handler newHandler(String tag) {
return new Handler(getHandlerThread(tag).getLooper());
}
private final HashMap<String, HandlerThread> threads = new HashMap<String, HandlerThread>();
private final HandlerThread getHandlerThread(String tag) {
HandlerThread handlerThread = null;
synchronized (threads) {
handlerThread = threads.get(tag);
if (handlerThread == null) {
handlerThread = new HandlerThread(nameOfTag(tag));
handlerThread.start();
threads.put(tag, handlerThread);
}
}
return handlerThread;
}
private final static String nameOfTag(String tag) {
return "HT-" + (TextUtils.isEmpty(tag) ? DEFAULT_TAG : tag);
}
}
package com.yidianling.avchatkit.common;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import com.yidianling.avchatkit.common.activity.AVChatBaseUI;
import com.yidianling.avchatkit.common.log.LogUtil;
public abstract class TFragment extends Fragment {
private static final Handler handler = new Handler();
private int containerId;
private boolean destroyed;
protected final boolean isDestroyed() {
return destroyed;
}
public int getContainerId() {
return containerId;
}
public void setContainerId(int containerId) {
this.containerId = containerId;
}
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
LogUtil.ui("fragment: " + getClass().getSimpleName() + " onActivityCreated()");
destroyed = false;
}
public void onDestroy() {
super.onDestroy();
LogUtil.ui("fragment: " + getClass().getSimpleName() + " onDestroy()");
destroyed = true;
}
protected final Handler getHandler() {
return handler;
}
protected final void postRunnable(final Runnable runnable) {
handler.post(new Runnable() {
@Override
public void run() {
// validate
if (!isAdded()) {
return;
}
// run
runnable.run();
}
});
}
protected final void postDelayed(final Runnable runnable, long delay) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
// validate
if (!isAdded()) {
return;
}
// run
runnable.run();
}
}, delay);
}
protected void showKeyboard(boolean isShow) {
Activity activity = getActivity();
if (activity == null) {
return;
}
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm == null) {
return;
}
if (isShow) {
if (activity.getCurrentFocus() == null) {
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
} else {
imm.showSoftInput(activity.getCurrentFocus(), 0);
}
} else {
if (activity.getCurrentFocus() != null) {
imm.hideSoftInputFromWindow(activity.getCurrentFocus().getWindowToken(),
InputMethodManager.HIDE_NOT_ALWAYS);
}
}
}
protected void hideKeyboard(View view) {
Activity activity = getActivity();
if (activity == null) {
return;
}
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm == null) {
return;
}
imm.hideSoftInputFromWindow(
view.getWindowToken(),
InputMethodManager.HIDE_NOT_ALWAYS);
}
protected <T extends View> T findView(int resId) {
return (T) (getView().findViewById(resId));
}
protected void setToolBar(int toolbarId, int titleId, int logoId) {
if (getActivity() != null && getActivity() instanceof AVChatBaseUI) {
((AVChatBaseUI) getActivity()).setToolBar(toolbarId, titleId, logoId);
}
}
protected void setTitle(int titleId) {
if (getActivity() != null && getActivity() instanceof AVChatBaseUI) {
getActivity().setTitle(titleId);
}
}
}
package com.yidianling.avchatkit.common.activity;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import com.yidianling.avchatkit.common.TFragment;
import com.yidianling.avchatkit.common.log.LogUtil;
import com.yidianling.avchatkit.common.util.ReflectionUtil;
import java.util.ArrayList;
import java.util.List;
public abstract class AVChatBaseUI extends AppCompatActivity {
private boolean destroyed = false;
private static Handler handler;
private Toolbar toolbar;
@Override
protected void onStart() {
super.onStart();
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtil.ui("activity: " + getClass().getSimpleName() + " onCreate()");
}
@Override
public void onBackPressed() {
invokeFragmentManagerNoteStateNotSaved();
super.onBackPressed();
}
@Override
protected void onDestroy() {
super.onDestroy();
LogUtil.ui("activity: " + getClass().getSimpleName() + " onDestroy()");
destroyed = true;
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onNavigateUpClicked();
return true;
}
return super.onOptionsItemSelected(item);
}
public void setToolBar(int toolBarId, ToolBarOptions options) {
toolbar = (Toolbar) findViewById(toolBarId);
if (options.titleId != 0) {
toolbar.setTitle(options.titleId);
}
if (!TextUtils.isEmpty(options.titleString)) {
toolbar.setTitle(options.titleString);
}
if (options.logoId != 0) {
toolbar.setLogo(options.logoId);
}
setSupportActionBar(toolbar);
if (options.isNeedNavigate) {
toolbar.setNavigationIcon(options.navigateId);
toolbar.setContentInsetStartWithNavigation(0);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onNavigateUpClicked();
}
});
}
}
public void setToolBar(int toolbarId, int titleId, int logoId) {
toolbar = (Toolbar) findViewById(toolbarId);
toolbar.setTitle(titleId);
toolbar.setLogo(logoId);
setSupportActionBar(toolbar);
}
public Toolbar getToolBar() {
return toolbar;
}
public int getToolBarHeight() {
if (toolbar != null) {
return toolbar.getHeight();
}
return 0;
}
public void onNavigateUpClicked() {
onBackPressed();
}
@Override
public void setTitle(CharSequence title) {
super.setTitle(title);
if (toolbar != null) {
toolbar.setTitle(title);
}
}
public void setSubTitle(String subTitle) {
if (toolbar != null) {
toolbar.setSubtitle(subTitle);
}
}
protected final Handler getHandler() {
if (handler == null) {
handler = new Handler(getMainLooper());
}
return handler;
}
protected void showKeyboard(boolean isShow) {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (isShow) {
if (getCurrentFocus() == null) {
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
} else {
imm.showSoftInput(getCurrentFocus(), 0);
}
} else {
if (getCurrentFocus() != null) {
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
}
}
}
/**
* 延时弹出键盘
*
* @param focus 键盘的焦点项
*/
protected void showKeyboardDelayed(View focus) {
final View viewToFocus = focus;
if (focus != null) {
focus.requestFocus();
}
getHandler().postDelayed(new Runnable() {
@Override
public void run() {
if (viewToFocus == null || viewToFocus.isFocused()) {
showKeyboard(true);
}
}
}, 200);
}
public boolean isDestroyedCompatible() {
if (Build.VERSION.SDK_INT >= 17) {
return isDestroyedCompatible17();
} else {
return destroyed || super.isFinishing();
}
}
@TargetApi(17)
private boolean isDestroyedCompatible17() {
return super.isDestroyed();
}
/**
* fragment management
*/
public TFragment addFragment(TFragment fragment) {
List<TFragment> fragments = new ArrayList<TFragment>(1);
fragments.add(fragment);
List<TFragment> fragments2 = addFragments(fragments);
return fragments2.get(0);
}
public List<TFragment> addFragments(List<TFragment> fragments) {
List<TFragment> fragments2 = new ArrayList<TFragment>(fragments.size());
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction transaction = fm.beginTransaction();
boolean commit = false;
for (int i = 0; i < fragments.size(); i++) {
// install
TFragment fragment = fragments.get(i);
int id = fragment.getContainerId();
// exists
TFragment fragment2 = (TFragment) fm.findFragmentById(id);
if (fragment2 == null) {
fragment2 = fragment;
transaction.add(id, fragment);
commit = true;
}
fragments2.add(i, fragment2);
}
if (commit) {
try {
transaction.commitAllowingStateLoss();
} catch (Exception e) {
}
}
return fragments2;
}
public TFragment switchContent(TFragment fragment) {
return switchContent(fragment, false);
}
protected TFragment switchContent(TFragment fragment, boolean needAddToBackStack) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fm.beginTransaction();
fragmentTransaction.replace(fragment.getContainerId(), fragment);
if (needAddToBackStack) {
fragmentTransaction.addToBackStack(null);
}
try {
fragmentTransaction.commitAllowingStateLoss();
} catch (Exception e) {
}
return fragment;
}
protected boolean displayHomeAsUpEnabled() {
return true;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_MENU:
return onMenuKeyDown();
default:
return super.onKeyDown(keyCode, event);
}
}
protected boolean onMenuKeyDown() {
return false;
}
private void invokeFragmentManagerNoteStateNotSaved() {
FragmentManager fm = getSupportFragmentManager();
ReflectionUtil.invokeMethod(fm, "noteStateNotSaved", null);
}
protected void switchFragmentContent(TFragment fragment) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction transaction = fm.beginTransaction();
transaction.replace(fragment.getContainerId(), fragment);
try {
transaction.commitAllowingStateLoss();
} catch (Exception e) {
e.printStackTrace();
}
}
protected boolean isCompatible(int apiLevel) {
return Build.VERSION.SDK_INT >= apiLevel;
}
protected <T extends View> T findView(int resId) {
return (T) (findViewById(resId));
}
}
package com.yidianling.avchatkit.common.activity;
import com.yidianling.im.R;
/**
* Created by hzxuwen on 2016/6/16.
*/
public class NimToolBarOptions extends ToolBarOptions {
public NimToolBarOptions() {
logoId = R.drawable.nim_actionbar_nest_dark_logo;
navigateId = R.drawable.nim_actionbar_dark_back_icon;
isNeedNavigate = true;
}
}
package com.yidianling.avchatkit.common.activity;
/**
* Created by hzxuwen on 2016/6/16.
*/
public class ToolBarOptions {
/**
* toolbar的title资源id
*/
public int titleId;
/**
* toolbar的title
*/
public String titleString;
/**
* toolbar的logo资源id
*/
public int logoId;
/**
* toolbar的返回按钮资源id
*/
public int navigateId;
/**
* toolbar的返回按钮
*/
public boolean isNeedNavigate;
}
package com.yidianling.avchatkit.common.adapter;
public interface IScrollStateListener {
/**
* move to scrap heap
*/
public void reclaim();
/**
* on idle
*/
public void onImmutable();
}
package com.yidianling.avchatkit.common.adapter;
import android.view.View;
public interface IViewReclaimer {
/**
* reclaim view
*
* @param view
*/
public void reclaimView(View view);
}
package com.yidianling.avchatkit.common.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import com.yidianling.avchatkit.common.log.LogUtil;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class TAdapter<T> extends BaseAdapter implements IViewReclaimer {
protected final Context context;
private final List<T> items;
private final TAdapterDelegate delegate;
private final LayoutInflater inflater;
private final Map<Class<?>, Integer> viewTypes;
private Object tag;
private boolean mutable;
private Set<IScrollStateListener> listeners;
public TAdapter(Context context, List<T> items, TAdapterDelegate delegate) {
this.context = context;
this.items = items;
this.delegate = delegate;
this.inflater = LayoutInflater.from(context);
this.viewTypes = new HashMap<Class<?>, Integer>(getViewTypeCount());
this.listeners = new HashSet<IScrollStateListener>();
}
@Override
public int getCount() {
return items == null ? 0 : items.size();
}
public T getItem(int position) {
return position < getCount() ? items.get(position) : null;
}
public long getItemId(int position) {
return position;
}
public boolean isEnabled(int position) {
return delegate.enabled(position);
}
public List<T> getItems() {
return items;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return getView(position, convertView, parent, true);
}
public View getView(final int position, View convertView, ViewGroup parent, boolean needRefresh) {
if (convertView == null) {
convertView = viewAtPosition(position);
}
TViewHolder holder = (TViewHolder) convertView.getTag();
holder.setPosition(position);
if (needRefresh) {
try {
holder.refresh(getItem(position));
} catch (RuntimeException e) {
LogUtil.e("TAdapter", "refresh viewholder error. " + e.getMessage());
}
}
if (holder instanceof IScrollStateListener) {
listeners.add(holder);
}
return convertView;
}
@Override
public int getViewTypeCount() {
return delegate.getViewTypeCount();
}
@Override
public int getItemViewType(int position) {
if (getViewTypeCount() == 1) {
return 0;
}
Class<?> clazz = delegate.viewHolderAtPosition(position);
if (viewTypes.containsKey(clazz)) {
return viewTypes.get(clazz);
} else {
int type = viewTypes.size();
if (type < getViewTypeCount()) {
viewTypes.put(clazz, type);
return type;
}
return 0;
}
}
@Override
public void reclaimView(View view) {
if (view == null) {
return;
}
TViewHolder holder = (TViewHolder) view.getTag();
if (holder != null) {
holder.reclaim();
listeners.remove(holder);
}
}
public void onMutable(boolean mutable) {
boolean becomeImmutable = this.mutable && !mutable;
this.mutable = mutable;
if (becomeImmutable) {
for (IScrollStateListener listener : listeners) {
listener.onImmutable();
}
listeners.clear();
}
}
public boolean isMutable() {
return mutable;
}
public View viewAtPosition(int position) {
TViewHolder holder = null;
View view = null;
try {
Class<?> viewHolder = delegate.viewHolderAtPosition(position);
holder = (TViewHolder) viewHolder.newInstance();
holder.setAdapter(this);
} catch (Exception e) {
e.printStackTrace();
}
view = holder.getView(inflater);
view.setTag(holder);
holder.setContext(view.getContext());
return view;
}
public Object getTag() {
return tag;
}
public void setTag(Object tag) {
this.tag = tag;
}
}
package com.yidianling.avchatkit.common.adapter;
public interface TAdapterDelegate {
public int getViewTypeCount();
public Class<? extends TViewHolder> viewHolderAtPosition(int position);
public boolean enabled(int position);
}
\ No newline at end of file
package com.yidianling.avchatkit.common.adapter;
import android.content.Context;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
public abstract class TViewHolder implements IScrollStateListener {
/**
* context
*/
protected Context context;
/**
* fragment
*/
protected Fragment fragment;
/**
* list item view
*/
protected View view;
/**
* adapter providing data
*/
protected TAdapter adapter;
/**
* index of item
*/
protected int position;
public TViewHolder() {
}
protected void setFragment(Fragment fragment) {
this.fragment = fragment;
}
protected void setContext(Context context) {
this.context = context;
}
protected void setAdapter(TAdapter adapter) {
this.adapter = adapter;
}
protected TAdapter getAdapter() {
return this.adapter;
}
protected void setPosition(int position) {
this.position = position;
}
public View getView(LayoutInflater inflater) {
int resId = getResId();
view = inflater.inflate(resId, null);
inflate();
return view;
}
public boolean isFirstItem() {
return position == 0;
}
public boolean isLastItem() {
return position == adapter.getCount() - 1;
}
protected abstract int getResId();
protected abstract void inflate();
protected abstract void refresh(Object item);
@Override
public void reclaim() {
}
@Override
public void onImmutable() {
}
protected boolean mutable() {
return adapter.isMutable();
}
public void destory() {
}
protected <T extends View> T findView(int resId) {
return (T) (view.findViewById(resId));
}
}
\ No newline at end of file
package com.yidianling.avchatkit.common.dialog;
import android.app.AlertDialog;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import com.yidianling.im.R;
import com.yidianling.avchatkit.common.adapter.TAdapter;
import com.yidianling.avchatkit.common.adapter.TAdapterDelegate;
import com.yidianling.avchatkit.common.adapter.TViewHolder;
import com.yidianling.avchatkit.common.util.ScreenUtil;
import java.util.LinkedList;
import java.util.List;
public class CustomAlertDialog extends AlertDialog {
private Context context;
private int itemSize = 0;
private View titleView;
private TextView titleTextView;
private ImageButton titleBtn;
private ListView listView;
private boolean isTitleVisible = false;
private boolean isTitleBtnVisible = false;
private String title;
private View.OnClickListener titleListener = null;
private List<Pair<String, Integer>> itemTextList = new LinkedList<Pair<String, Integer>>();
private List<onSeparateItemClickListener> itemListenerList = new LinkedList<onSeparateItemClickListener>();
private OnClickListener listListener;
private BaseAdapter listAdapter;
private OnItemClickListener itemListener;
private int defaultColor = R.color.platform_color_black_333333;
public CustomAlertDialog(Context context) {
super(context, R.style.dialog_default_style);
this.context = context;
initAdapter();
}
public CustomAlertDialog(Context context, int itemSize) {
super(context, R.style.dialog_default_style);
this.context = context;
this.itemSize = itemSize;
}
private void initAdapter() {
listAdapter = new TAdapter(context, itemTextList, new TAdapterDelegate() {
@Override
public int getViewTypeCount() {
return itemTextList.size();
}
@Override
public Class<? extends TViewHolder> viewHolderAtPosition(int position) {
return CustomDialogViewHolder.class;
}
@Override
public boolean enabled(int position) {
return true;
}
});
itemListener = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
itemListenerList.get(position).onClick();
dismiss();
}
};
}
public void setAdapter(final BaseAdapter adapter, final OnClickListener listener) {
listAdapter = adapter;
listListener = listener;
itemListener = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
dismiss();
listListener.onClick(CustomAlertDialog.this, position);
}
};
}
public void setAdapter(final BaseAdapter adapter, final OnItemClickListener listener) {
listAdapter = adapter;
itemListener = listener;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.nim_easy_alert_dialog_with_listview);
LinearLayout root = (LinearLayout) findViewById(R.id.easy_alert_dialog_layout);
ViewGroup.LayoutParams params = root.getLayoutParams();
params.width = (int) ScreenUtil.getDialogWidth();
root.setLayoutParams(params);
addFootView(root);
titleView = findViewById(R.id.easy_dialog_title_view);
if (titleView != null) {
setTitleVisible(isTitleVisible);
}
titleTextView = (TextView) findViewById(R.id.easy_dialog_title_text_view);
if (titleTextView != null) {
setTitle(title);
}
titleBtn = (ImageButton) findViewById(R.id.easy_dialog_title_button);
if (titleBtn != null) {
setTitleBtnVisible(isTitleBtnVisible);
setTitleBtnListener(titleListener);
}
listView = (ListView) findViewById(R.id.easy_dialog_list_view);
if (itemSize > 0) {
updateListView();
}
}
protected void addFootView(LinearLayout parent) {
}
public void setTitle(String title) {
this.title = title;
isTitleVisible = TextUtils.isEmpty(title) ? false : true;
setTitleVisible(isTitleVisible);
if (isTitleVisible && titleTextView != null) {
titleTextView.setText(title);
}
}
public void setTitle(int resId) {
this.title = context.getString(resId);
isTitleVisible = TextUtils.isEmpty(title) ? false : true;
setTitleVisible(isTitleVisible);
if (isTitleVisible && titleTextView != null) {
titleTextView.setText(title);
}
}
public void setTitleVisible(boolean visible) {
isTitleVisible = visible;
if (titleView != null) {
titleView.setVisibility(isTitleVisible ? View.VISIBLE : View.GONE);
}
}
public void setTitleBtnVisible(boolean visible) {
isTitleBtnVisible = visible;
if (titleBtn != null) {
titleBtn.setVisibility(isTitleBtnVisible ? View.VISIBLE : View.GONE);
}
}
public void setTitleBtnListener(View.OnClickListener titleListener) {
this.titleListener = titleListener;
if (titleListener != null && titleBtn != null) {
titleBtn.setOnClickListener(titleListener);
}
}
public void addItem(String itemText, onSeparateItemClickListener listener) {
addItem(itemText, defaultColor, listener);
}
public void addItem(String itemText, int color, onSeparateItemClickListener listener) {
itemTextList.add(new Pair<String, Integer>(itemText, color));
itemListenerList.add(listener);
itemSize = itemTextList.size();
}
public void addItem(int resId, onSeparateItemClickListener listener) {
addItem(context.getString(resId), listener);
}
public void addItem(int resId, int color, onSeparateItemClickListener listener) {
addItem(context.getString(resId), color, listener);
}
public void addItemAfterAnother(String itemText, String another, onSeparateItemClickListener listener) {
int index = itemTextList.indexOf(another);
itemTextList.add(index + 1, new Pair<String, Integer>(itemText, defaultColor));
itemListenerList.add(index + 1, listener);
itemSize = itemTextList.size();
}
public void clearData() {
itemTextList.clear();
itemListenerList.clear();
itemSize = 0;
}
private void updateListView() {
listAdapter.notifyDataSetChanged();
if (listView != null) {
listView.setAdapter(listAdapter);
listView.setOnItemClickListener(itemListener);
}
}
public interface onSeparateItemClickListener {
void onClick();
}
@Override
public void show() {
if (itemSize <= 0) {
return;
}
updateListView();
super.show();
}
}
package com.yidianling.avchatkit.common.dialog;
import android.util.Pair;
import android.widget.TextView;
import com.yidianling.im.R;
import com.yidianling.avchatkit.common.adapter.TViewHolder;
public class CustomDialogViewHolder extends TViewHolder {
private TextView itemView;
@Override
protected int getResId() {
return R.layout.nim_custom_dialog_list_item;
}
@Override
protected void inflate() {
itemView = (TextView) view.findViewById(R.id.custom_dialog_text_view);
}
@Override
protected void refresh(Object item) {
if (item instanceof Pair<?, ?>) {
Pair<String, Integer> pair = (Pair<String, Integer>) item;
itemView.setText(pair.first);
itemView.setTextColor(context.getResources().getColor(pair.second));
}
}
}
package com.yidianling.avchatkit.common.dialog;
import android.content.Context;
import android.content.DialogInterface.OnCancelListener;
import android.text.TextUtils;
import com.yidianling.avchatkit.common.log.LogUtil;
import java.lang.ref.WeakReference;
public class DialogMaker {
private static WeakReference<EasyProgressDialog> sProgressDialogRef;
public static EasyProgressDialog showProgressDialog(Context context, String message) {
return showProgressDialog(context, null, message, true, null);
}
public static EasyProgressDialog showProgressDialog(Context context, String message, boolean cancelable) {
return showProgressDialog(context, null, message, cancelable, null);
}
@Deprecated
public static EasyProgressDialog showProgressDialog(Context context, String title, String message,
boolean canCancelable, OnCancelListener listener) {
EasyProgressDialog dialog = getDialog();
if (dialog != null && dialog.getContext() != context) {
// maybe existing dialog is running in a destroyed activity cotext we should recreate one
dismissProgressDialog();
dialog = null;
LogUtil.e("dialog", "there is a leaked window here,orign context: " + dialog.getContext() + " now: " + context);
}
if (dialog == null) {
dialog = new EasyProgressDialog(context, message);
sProgressDialogRef = new WeakReference<>(dialog);
}
if (!TextUtils.isEmpty(title)) {
dialog.setTitle(title);
}
if (!TextUtils.isEmpty(message)) {
dialog.setMessage(message);
}
dialog.setCancelable(canCancelable);
dialog.setOnCancelListener(listener);
dialog.show();
return dialog;
}
public static void dismissProgressDialog() {
EasyProgressDialog dialog = getDialog();
if (null == dialog) {
return;
}
sProgressDialogRef.clear();
if (dialog.isShowing()) {
try {
dialog.dismiss();
} catch (Exception e) {
// maybe we catch IllegalArgumentException here.
}
}
}
public static void setMessage(String message) {
EasyProgressDialog dialog = getDialog();
if (null != dialog && dialog.isShowing() && !TextUtils.isEmpty(message)) {
dialog.setMessage(message);
}
}
public static void updateLoadingMessage(String message) {
EasyProgressDialog dialog = getDialog();
if (null != dialog && dialog.isShowing() && !TextUtils.isEmpty(message)) {
dialog.updateLoadingMessage(message);
}
}
public static boolean isShowing() {
EasyProgressDialog dialog = getDialog();
return (dialog != null && dialog.isShowing());
}
private static EasyProgressDialog getDialog() {
return sProgressDialogRef == null ? null : sProgressDialogRef.get();
}
}
package com.yidianling.avchatkit.common.dialog;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.WindowManager.LayoutParams;
import android.widget.TextView;
import com.yidianling.im.R;
/**
* 一个半透明窗口,包含一个Progressbar 和 Message部分. 其中Message部分可选. 可单独使用,也可以使用
* {@link DialogMaker} 进行相关窗口显示.
*
* @author Qijun
*/
public class EasyProgressDialog extends Dialog {
private Context mContext;
private String mMessage;
private int mLayoutId;
private TextView message;
public EasyProgressDialog(Context context, int style, int layout) {
super(context, style);
mContext = context;
LayoutParams Params = getWindow().getAttributes();
Params.width = LayoutParams.FILL_PARENT;
Params.height = LayoutParams.FILL_PARENT;
getWindow().setAttributes(Params);
mLayoutId = layout;
}
public EasyProgressDialog(Context context, int layout, String msg) {
this(context, R.style.easy_dialog_style, layout);
setMessage(msg);
}
public EasyProgressDialog(Context context, String msg) {
this(context, R.style.easy_dialog_style, R.layout.nim_easy_progress_dialog);
setMessage(msg);
}
public EasyProgressDialog(Context context) {
this(context, R.style.easy_dialog_style, R.layout.nim_easy_progress_dialog);
}
public void setMessage(String msg) {
mMessage = msg;
}
public void updateLoadingMessage(String msg) {
mMessage = msg;
showMessage();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(mLayoutId);
message = (TextView) findViewById(R.id.easy_progress_dialog_message);
showMessage();
}
private void showMessage() {
if (message != null && !TextUtils.isEmpty(mMessage)) {
message.setVisibility(View.VISIBLE);
message.setText(mMessage);
}
}
}
\ No newline at end of file
package com.yidianling.avchatkit.common.imageview;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import com.netease.nimlib.sdk.msg.constant.MsgTypeEnum;
import com.netease.nimlib.sdk.msg.model.IMMessage;
import com.netease.nimlib.sdk.nos.model.NosThumbParam;
import com.netease.nimlib.sdk.nos.util.NosThumbImageUtil;
import com.netease.nimlib.sdk.robot.model.RobotAttachment;
import com.netease.nimlib.sdk.team.model.Team;
import com.netease.nimlib.sdk.uinfo.model.UserInfo;
import com.yidianling.avchatkit.AVChatKit;
import com.yidianling.im.R;
/**
* Created by huangjun on 2015/11/13.
*/
public class HeadImageView extends CircleImageView {
public static final int DEFAULT_AVATAR_THUMB_SIZE = (int) AVChatKit.getContext().getResources().getDimension(R.dimen.avatar_max_size);
public static final int DEFAULT_AVATAR_NOTIFICATION_ICON_SIZE = (int) AVChatKit.getContext().getResources().getDimension(R.dimen.avatar_notification_size);
private static final int DEFAULT_AVATAR_RES_ID = R.drawable.nim_avatar_default;
public HeadImageView(Context context) {
super(context);
}
public HeadImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public HeadImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* 加载用户头像(默认大小的缩略图)
*
* @param url 头像地址
*/
public void loadAvatar(final String url) {
doLoadImage(url, DEFAULT_AVATAR_RES_ID, DEFAULT_AVATAR_THUMB_SIZE);
}
/**
* 加载用户头像(默认大小的缩略图)
*
* @param account 用户账号
*/
public void loadBuddyAvatar(String account) {
final UserInfo userInfo = AVChatKit.getUserInfoProvider().getUserInfo(account);
doLoadImage(userInfo != null ? userInfo.getAvatar() : null, DEFAULT_AVATAR_RES_ID, DEFAULT_AVATAR_THUMB_SIZE);
}
/**
* 加载用户头像(默认大小的缩略图)
*
* @param message 消息
*/
public void loadBuddyAvatar(IMMessage message) {
String account = message.getFromAccount();
if (message.getMsgType() == MsgTypeEnum.robot) {
RobotAttachment attachment = (RobotAttachment) message.getAttachment();
if (attachment.isRobotSend()) {
account = attachment.getFromRobotAccount();
}
}
loadBuddyAvatar(account);
}
/**
* 加载群头像(默认大小的缩略图)
*
* @param team 群
*/
public void loadTeamIconByTeam(final Team team) {
doLoadImage(team != null ? team.getIcon() : null, R.drawable.nim_avatar_group, DEFAULT_AVATAR_THUMB_SIZE);
}
/**
* ImageLoader异步加载
*/
private void doLoadImage(final String url, final int defaultResId, final int thumbSize) {
/*
* 若使用网易云信云存储,这里可以设置下载图片的压缩尺寸,生成下载URL
* 如果图片来源是非网易云信云存储,请不要使用NosThumbImageUtil
*/
final String thumbUrl = makeAvatarThumbNosUrl(url, thumbSize);
RequestOptions requestOptions = new RequestOptions()
.centerCrop()
.placeholder(defaultResId)
.error(defaultResId)
.override(thumbSize, thumbSize);
Glide.with(getContext().getApplicationContext()).asBitmap()
.load(thumbUrl)
.apply(requestOptions)
.into(this);
}
/**
* 解决ViewHolder复用问题
*/
public void resetImageView() {
setImageBitmap(null);
}
/**
* 生成头像缩略图NOS URL地址(用作ImageLoader缓存的key)
*/
private static String makeAvatarThumbNosUrl(final String url, final int thumbSize) {
if (TextUtils.isEmpty(url)) {
return url;
}
return thumbSize > 0 ? NosThumbImageUtil.makeImageThumbUrl(url, NosThumbParam.ThumbType.Crop, thumbSize, thumbSize) : url;
}
public static String getAvatarCacheKey(final String url) {
return makeAvatarThumbNosUrl(url, DEFAULT_AVATAR_THUMB_SIZE);
}
}
package com.yidianling.avchatkit.common.log;
/**
* Created by winnie on 2017/12/21.
*/
public interface ILogUtil {
void ui(String msg);
void e(String tag, String msg);
void i(String tag, String msg);
void d(String tag, String msg);
}
package com.yidianling.avchatkit.common.log;
import android.util.Log;
import com.yidianling.avchatkit.AVChatKit;
/**
* Created by winnie on 2017/12/19.
*/
public class LogUtil {
public static void ui(String msg) {
if (AVChatKit.getiLogUtil() == null) {
Log.i("ui", msg);
} else {
AVChatKit.getiLogUtil().ui(msg);
}
}
public static void e(String tag, String msg) {
if (AVChatKit.getiLogUtil() == null) {
Log.e(tag, msg);
} else {
AVChatKit.getiLogUtil().e(tag, msg);
}
}
public static void i(String tag, String msg) {
if (AVChatKit.getiLogUtil() == null) {
Log.i(tag, msg);
} else {
AVChatKit.getiLogUtil().i(tag, msg);
}
}
public static void d(String tag, String msg) {
if (AVChatKit.getiLogUtil() == null) {
Log.d(tag, msg);
} else {
AVChatKit.getiLogUtil().d(tag, msg);
}
}
}
package com.yidianling.avchatkit.common.permission;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.v4.app.Fragment;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
public class BaseMPermission {
private static final String TAG = "MPermission";
public enum MPermissionResultEnum {
GRANTED, DENIED, DENIED_NEVER_ASK_AGAIN
}
static boolean isOverMarshmallow() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}
static Activity getActivity(Object object) {
if (object instanceof Fragment) {
return ((Fragment) object).getActivity();
} else if (object instanceof Activity) {
return (Activity) object;
}
return null;
}
/**
* 获取权限请求结果
*/
public static List<MPermissionResultEnum> getPermissionResult(Activity activity, String[] permissions) {
return findPermissionResult(activity, permissions);
}
public static List<MPermissionResultEnum> getPermissionResult(Fragment fragment, String[] permissions) {
return findPermissionResult(fragment.getActivity(), permissions);
}
@TargetApi(value = Build.VERSION_CODES.M)
private static List<MPermissionResultEnum> findPermissionResult(Activity activity, String... permissions) {
boolean overM = isOverMarshmallow();
List<MPermissionResultEnum> result = new ArrayList<>();
for (String p : permissions) {
if (overM) {
if (activity.checkSelfPermission(p) == PackageManager.PERMISSION_GRANTED) {
result.add(MPermissionResultEnum.GRANTED);
} else {
if (!activity.shouldShowRequestPermissionRationale(p)) {
result.add(MPermissionResultEnum.DENIED_NEVER_ASK_AGAIN);
} else {
result.add(MPermissionResultEnum.DENIED);
}
}
} else {
result.add(MPermissionResultEnum.GRANTED);
}
}
return result;
}
/**
* 获取所有被未被授权的权限
*/
public static List<String> getDeniedPermissions(Activity activity, String[] permissions) {
return findDeniedPermissions(activity, permissions);
}
public static List<String> getDeniedPermissions(Fragment fragment, String[] permissions) {
return findDeniedPermissions(fragment.getActivity(), permissions);
}
@TargetApi(value = Build.VERSION_CODES.M)
static List<String> findDeniedPermissions(Activity activity, String... permissions) {
if (!isOverMarshmallow()) {
return null;
}
List<String> denyPermissions = new ArrayList<>();
for (String value : permissions) {
if (activity.checkSelfPermission(value) != PackageManager.PERMISSION_GRANTED) {
denyPermissions.add(value);
}
}
return denyPermissions;
}
/**
* 获取被拒绝且勾选不再询问的权限
* 请在请求权限结果回调中使用,因为从未请求过的权限也会被认为是该结果集
*/
public static List<String> getNeverAskAgainPermissions(Activity activity, String[] permissions) {
return findNeverAskAgainPermissions(activity, permissions);
}
public static List<String> getNeverAskAgainPermissions(Fragment fragment, String[] permissions) {
return findNeverAskAgainPermissions(fragment.getActivity(), permissions);
}
@TargetApi(value = Build.VERSION_CODES.M)
private static List<String> findNeverAskAgainPermissions(Activity activity, String... permissions) {
if (!isOverMarshmallow()) {
return null;
}
List<String> neverAskAgainPermission = new ArrayList<>();
for (String value : permissions) {
if (activity.checkSelfPermission(value) != PackageManager.PERMISSION_GRANTED &&
!activity.shouldShowRequestPermissionRationale(value)) {
// 拒绝&不要需要解释了(用户勾选了不再询问)
// 坑爹:第一次不做任何设置,返回值也是false。建议在权限授权结果里判断!!!
neverAskAgainPermission.add(value);
}
}
return neverAskAgainPermission;
}
@TargetApi(value = Build.VERSION_CODES.M)
static boolean hasNeverAskAgainPermission(Activity activity, List<String> permission) {
if (!isOverMarshmallow()) {
return false;
}
for (String value : permission) {
if (activity.checkSelfPermission(value) != PackageManager.PERMISSION_GRANTED &&
!activity.shouldShowRequestPermissionRationale(value)) {
return true;
}
}
return false;
}
/**
* 获取被拒绝但没有勾选不再询问的权限(可以继续申请,会继续弹框)
*/
public static List<String> getDeniedPermissionsWithoutNeverAskAgain(Activity activity, String[] permissions) {
return findDeniedPermissionWithoutNeverAskAgain(activity, permissions);
}
public static List<String> getDeniedPermissionsWithoutNeverAskAgain(Fragment fragment, String[] permissions) {
return findDeniedPermissionWithoutNeverAskAgain(fragment.getActivity(), permissions);
}
@TargetApi(value = Build.VERSION_CODES.M)
private static List<String> findDeniedPermissionWithoutNeverAskAgain(Activity activity, String... permission) {
if (!isOverMarshmallow()) {
return null;
}
List<String> denyPermissions = new ArrayList<>();
for (String value : permission) {
if (activity.checkSelfPermission(value) != PackageManager.PERMISSION_GRANTED &&
activity.shouldShowRequestPermissionRationale(value)) {
denyPermissions.add(value); // 上次申请被用户拒绝了
}
}
return denyPermissions;
}
/**
* Log专用
*/
public static void printMPermissionResult(boolean preRequest, Activity activity, String[] permissions) {
Log.i(TAG, "----- MPermission result " + (preRequest ? "before" : "after") + " request:");
List<MPermissionResultEnum> result = getPermissionResult(activity, permissions);
int i = 0;
for (BaseMPermission.MPermissionResultEnum p : result) {
Log.i(TAG, "* MPermission=" + permissions[i++] + ", result=" + p);
}
}
static String toString(List<String> permission) {
if (permission == null || permission.isEmpty()) {
return "";
}
return toString(permission.toArray(new String[permission.size()]));
}
private static String toString(String[] permission) {
if (permission == null || permission.length <= 0) {
return "";
}
StringBuilder sb = new StringBuilder();
for (String p : permission) {
sb.append(p.replaceFirst("android.permission.", ""));
sb.append(",");
}
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
}
package com.yidianling.avchatkit.common.permission;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import com.yidianling.avchatkit.common.permission.annotation.OnMPermissionDenied;
import com.yidianling.avchatkit.common.permission.annotation.OnMPermissionGranted;
import com.yidianling.avchatkit.common.permission.annotation.OnMPermissionNeverAskAgain;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class MPermission extends BaseMPermission {
private int requestCode;
private String[] permissions;
private Object object; // activity or fragment
private MPermission(Object object) {
this.object = object;
}
public static MPermission with(Activity activity) {
return new MPermission(activity);
}
public static MPermission with(Fragment fragment) {
return new MPermission(fragment);
}
public MPermission setRequestCode(int requestCode) {
this.requestCode = requestCode;
return this;
}
public MPermission permissions(String... permissions) {
this.permissions = permissions;
return this;
}
/**
* ********************* request *********************
*/
@TargetApi(value = Build.VERSION_CODES.M)
public void request() {
doRequestPermissions(object, requestCode, permissions);
}
@TargetApi(value = Build.VERSION_CODES.M)
private static void doRequestPermissions(Object object, int requestCode, String[] permissions) {
if (!isOverMarshmallow()) {
doExecuteSuccess(object, requestCode);
return;
}
List<String> deniedPermissions = findDeniedPermissions(getActivity(object), permissions);
if (deniedPermissions != null && deniedPermissions.size() > 0) {
if (object instanceof Activity) {
((Activity) object).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);
} else if (object instanceof Fragment) {
((Fragment) object).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);
} else {
throw new IllegalArgumentException(object.getClass().getName() + " is not supported");
}
} else {
doExecuteSuccess(object, requestCode);
}
}
/**
* ********************* on result *********************
*/
public static void onRequestPermissionsResult(Activity activity, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
dispatchResult(activity, requestCode, permissions, grantResults);
}
public static void onRequestPermissionsResult(Fragment fragment, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
dispatchResult(fragment, requestCode, permissions, grantResults);
}
private static void dispatchResult(Object obj, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
List<String> deniedPermissions = new ArrayList<>();
for (int i = 0; i < grantResults.length; i++) {
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
deniedPermissions.add(permissions[i]);
}
}
if (deniedPermissions.size() > 0) {
if (hasNeverAskAgainPermission(getActivity(obj), deniedPermissions)) {
doExecuteFailAsNeverAskAgain(obj, requestCode);
} else {
doExecuteFail(obj, requestCode);
}
} else {
doExecuteSuccess(obj, requestCode);
}
}
/**
* ********************* reflect execute result *********************
*/
private static void doExecuteSuccess(Object activity, int requestCode) {
executeMethod(activity, findMethodWithRequestCode(activity.getClass(), OnMPermissionGranted.class, requestCode));
}
private static void doExecuteFail(Object activity, int requestCode) {
executeMethod(activity, findMethodWithRequestCode(activity.getClass(), OnMPermissionDenied.class, requestCode));
}
private static void doExecuteFailAsNeverAskAgain(Object activity, int requestCode) {
executeMethod(activity, findMethodWithRequestCode(activity.getClass(), OnMPermissionNeverAskAgain.class, requestCode));
}
private static <A extends Annotation> Method findMethodWithRequestCode(Class clazz, Class<A> annotation, int
requestCode) {
for (Method method : clazz.getDeclaredMethods()) {
if (method.getAnnotation(annotation) != null &&
isEqualRequestCodeFromAnnotation(method, annotation, requestCode)) {
return method;
}
}
return null;
}
private static boolean isEqualRequestCodeFromAnnotation(Method m, Class clazz, int requestCode) {
if (clazz.equals(OnMPermissionDenied.class)) {
return requestCode == m.getAnnotation(OnMPermissionDenied.class).value();
} else if (clazz.equals(OnMPermissionGranted.class)) {
return requestCode == m.getAnnotation(OnMPermissionGranted.class).value();
} else if (clazz.equals(OnMPermissionNeverAskAgain.class)) {
return requestCode == m.getAnnotation(OnMPermissionNeverAskAgain.class).value();
} else {
return false;
}
}
/**
* ********************* reflect execute method *********************
*/
private static void executeMethod(Object activity, Method executeMethod) {
executeMethodWithParam(activity, executeMethod, new Object[]{});
}
private static void executeMethodWithParam(Object activity, Method executeMethod, Object... args) {
if (executeMethod != null) {
try {
if (!executeMethod.isAccessible()) {
executeMethod.setAccessible(true);
}
executeMethod.invoke(activity, args);
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
package com.yidianling.avchatkit.common.permission.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* register a method invoked when permission requests are denied without check never ask again.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnMPermissionDenied {
int value();
}
\ No newline at end of file
package com.yidianling.avchatkit.common.permission.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* register a method invoked when permission requests are succeeded.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnMPermissionGranted {
int value();
}
package com.yidianling.avchatkit.common.permission.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* register some methods handling the user's choice to permanently deny permissions checking never ask again.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnMPermissionNeverAskAgain {
int value();
}
package com.yidianling.avchatkit.common.recyclerview.adapter;
import android.support.annotation.LayoutRes;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.ViewGroup;
import com.yidianling.avchatkit.common.recyclerview.holder.BaseViewHolder;
import com.yidianling.avchatkit.common.recyclerview.holder.RecyclerViewHolder;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public abstract class BaseMultiItemFetchLoadAdapter<T, K extends BaseViewHolder> extends BaseFetchLoadAdapter<T, K> {
/**
* viewType->layoutResId
*/
private SparseArray<Integer> layouts;
/**
* viewType->view holder class
*/
private SparseArray<Class<? extends RecyclerViewHolder>> holderClasses;
/**
* viewType->view holder instance
*/
private Map<Integer, Map<String, RecyclerViewHolder>> multiTypeViewHolders;
/**
* get view type from data item
*
* @param item
* @return
*/
protected abstract int getViewType(T item);
/**
* get view holder unique key from data item
*
* @param item
* @return
*/
protected abstract String getItemKey(T item);
public BaseMultiItemFetchLoadAdapter(RecyclerView recyclerView, List<T> data) {
super(recyclerView, 0, data);
}
/**
* add viewType->layoutResId, viewType->ViewHolder.class
*
* @param type view type
* @param layoutResId
* @param viewHolderClass
*/
protected void addItemType(int type, @LayoutRes int layoutResId, Class<? extends RecyclerViewHolder> viewHolderClass) {
// layouts
if (layouts == null) {
layouts = new SparseArray<>();
}
layouts.put(type, layoutResId);
// view holder class
if (holderClasses == null) {
holderClasses = new SparseArray<>();
}
holderClasses.put(type, viewHolderClass);
// view holder
if (multiTypeViewHolders == null) {
multiTypeViewHolders = new HashMap<>();
}
multiTypeViewHolders.put(type, new HashMap<String, RecyclerViewHolder>());
}
@Override
protected int getDefItemViewType(int position) {
return getViewType(mData.get(position));
}
@Override
protected K onCreateDefViewHolder(ViewGroup parent, int viewType) {
return createBaseViewHolder(parent, getLayoutId(viewType));
}
@Override
protected void convert(final K baseHolder, final T item, final int position, boolean isScrolling) {
final String key = getItemKey(item);
final int viewType = baseHolder.getItemViewType();
RecyclerViewHolder h = multiTypeViewHolders.get(viewType).get(key);
if (h == null) {
// build
try {
Class<? extends RecyclerViewHolder> cls = holderClasses.get(viewType);
Constructor c = cls.getDeclaredConstructors()[0]; // 第一个显式的构造函数
c.setAccessible(true);
h = (RecyclerViewHolder) c.newInstance(new Object[]{this});
multiTypeViewHolders.get(viewType).put(key, h);
} catch (Exception e) {
e.printStackTrace();
}
}
// convert
if (h != null) {
h.convert(baseHolder, item, position, isScrolling);
}
}
@Override
protected void onRemove(final T item) {
super.onRemove(item);
// 移除holder
multiTypeViewHolders.get(getViewType(item)).remove(getItemKey(item));
}
private int getLayoutId(int viewType) {
return layouts.get(viewType);
}
protected RecyclerViewHolder getViewHolder(int viewType, String viewHolderKey) {
if (multiTypeViewHolders.containsKey(viewType)) {
return multiTypeViewHolders.get(viewType).get(viewHolderKey);
}
return null;
}
}
package com.yidianling.avchatkit.common.recyclerview.adapter;
/**
* Created by huangjun on 2016/12/8.
*/
public interface IRecyclerView {
/**
* special view type
*/
int FETCHING_VIEW = 0x00001000;
int HEADER_VIEW = 0x00001001;
int LOADING_VIEW = 0x00001002;
int FOOTER_VIEW = 0x00001003;
int EMPTY_VIEW = 0x00001004;
/**
* 获取Header item的数量(包含FetchItem)
*/
int getHeaderLayoutCount();
/**
* 获取Item视图类型
*
* @param position Item位置
* @return
*/
int getItemViewType(int position);
}
package com.yidianling.avchatkit.common.recyclerview.animation;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.view.View;
public class AlphaInAnimation implements BaseAnimation {
private static final float DEFAULT_ALPHA_FROM = 0f;
private final float mFrom;
public AlphaInAnimation() {
this(DEFAULT_ALPHA_FROM);
}
public AlphaInAnimation(float from) {
mFrom = from;
}
@Override
public Animator[] getAnimators(View view) {
return new Animator[]{ObjectAnimator.ofFloat(view, "alpha", mFrom, 1f)};
}
}
package com.yidianling.avchatkit.common.recyclerview.animation;
import android.animation.Animator;
import android.view.View;
public interface BaseAnimation {
Animator[] getAnimators(View view);
}
package com.yidianling.avchatkit.common.recyclerview.animation;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.view.View;
public class ScaleInAnimation implements BaseAnimation {
private static final float DEFAULT_SCALE_FROM = .5f;
private final float mFrom;
public ScaleInAnimation() {
this(DEFAULT_SCALE_FROM);
}
public ScaleInAnimation(float from) {
mFrom = from;
}
@Override
public Animator[] getAnimators(View view) {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", mFrom, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", mFrom, 1f);
return new ObjectAnimator[]{scaleX, scaleY};
}
}
package com.yidianling.avchatkit.common.recyclerview.animation;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.view.View;
public class SlideInBottomAnimation implements BaseAnimation {
@Override
public Animator[] getAnimators(View view) {
return new Animator[]{
ObjectAnimator.ofFloat(view, "translationY", view.getMeasuredHeight(), 0)
};
}
}
package com.yidianling.avchatkit.common.recyclerview.animation;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.view.View;
public class SlideInLeftAnimation implements BaseAnimation {
@Override
public Animator[] getAnimators(View view) {
return new Animator[]{
ObjectAnimator.ofFloat(view, "translationX", -view.getRootView().getWidth(), 0)
};
}
}
package com.yidianling.avchatkit.common.recyclerview.animation;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.view.View;
public class SlideInRightAnimation implements BaseAnimation {
@Override
public Animator[] getAnimators(View view) {
return new Animator[]{
ObjectAnimator.ofFloat(view, "translationX", view.getRootView().getWidth(), 0)
};
}
}
package com.yidianling.avchatkit.common.recyclerview.decoration;
import android.graphics.Rect;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.View;
/**
* Created by huangjun on 2016/12/9.
*/
public class SpacingDecoration extends RecyclerView.ItemDecoration {
private int mHorizontalSpacing = 0;
private int mVerticalSpacing = 0;
private boolean mIncludeEdge = false;
public SpacingDecoration(int hSpacing, int vSpacing, boolean includeEdge) {
mHorizontalSpacing = hSpacing;
mVerticalSpacing = vSpacing;
mIncludeEdge = includeEdge;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
// Only handle the vertical situation
int position = parent.getChildAdapterPosition(view);
if (parent.getLayoutManager() instanceof GridLayoutManager) {
GridLayoutManager layoutManager = (GridLayoutManager) parent.getLayoutManager();
int spanCount = layoutManager.getSpanCount();
int column = position % spanCount;
getGridItemOffsets(outRect, position, column, spanCount);
} else if (parent.getLayoutManager() instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) parent.getLayoutManager();
int spanCount = layoutManager.getSpanCount();
StaggeredGridLayoutManager.LayoutParams lp = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
int column = lp.getSpanIndex();
getGridItemOffsets(outRect, position, column, spanCount);
} else if (parent.getLayoutManager() instanceof LinearLayoutManager) {
outRect.left = mHorizontalSpacing;
outRect.right = mHorizontalSpacing;
if (mIncludeEdge) {
if (position == 0) {
outRect.top = mVerticalSpacing;
}
outRect.bottom = mVerticalSpacing;
} else {
if (position > 0) {
outRect.top = mVerticalSpacing;
}
}
}
}
private void getGridItemOffsets(Rect outRect, int position, int column, int spanCount) {
if (mIncludeEdge) {
outRect.left = mHorizontalSpacing * (spanCount - column) / spanCount;
outRect.right = mHorizontalSpacing * (column + 1) / spanCount;
if (position < spanCount) {
outRect.top = mVerticalSpacing;
}
outRect.bottom = mVerticalSpacing;
} else {
outRect.left = mHorizontalSpacing * column / spanCount;
outRect.right = mHorizontalSpacing * (spanCount - 1 - column) / spanCount;
if (position >= spanCount) {
outRect.top = mVerticalSpacing;
}
}
}
}
package com.yidianling.avchatkit.common.recyclerview.holder;
import android.support.v7.widget.RecyclerView;
/**
* Created by huangjun on 2016/12/11.
*/
public abstract class RecyclerViewHolder<T extends RecyclerView.Adapter, V extends BaseViewHolder, K> {
final private T adapter;
public RecyclerViewHolder(T adapter) {
this.adapter = adapter;
}
public T getAdapter() {
return adapter;
}
public abstract void convert(V holder, K data, int position, boolean isScrolling);
}
package com.yidianling.avchatkit.common.recyclerview.loadmore;
import android.support.annotation.IdRes;
import android.support.annotation.LayoutRes;
import com.yidianling.avchatkit.common.recyclerview.holder.BaseViewHolder;
public abstract class LoadMoreView {
public static final int STATUS_DEFAULT = 1;
public static final int STATUS_LOADING = 2;
public static final int STATUS_FAIL = 3;
public static final int STATUS_END = 4;
private int mLoadMoreStatus = STATUS_DEFAULT;
private boolean mLoadMoreEndGone = false;
public void setLoadMoreStatus(int loadMoreStatus) {
this.mLoadMoreStatus = loadMoreStatus;
}
public int getLoadMoreStatus() {
return mLoadMoreStatus;
}
public void convert(BaseViewHolder holder) {
switch (mLoadMoreStatus) {
case STATUS_LOADING:
visibleLoading(holder, true);
visibleLoadFail(holder, false);
visibleLoadEnd(holder, false);
break;
case STATUS_FAIL:
visibleLoading(holder, false);
visibleLoadFail(holder, true);
visibleLoadEnd(holder, false);
break;
case STATUS_END:
visibleLoading(holder, false);
visibleLoadFail(holder, false);
visibleLoadEnd(holder, true);
break;
}
}
private void visibleLoading(BaseViewHolder holder, boolean visible) {
holder.setVisible(getLoadingViewId(), visible);
}
private void visibleLoadFail(BaseViewHolder holder, boolean visible) {
holder.setVisible(getLoadFailViewId(), visible);
}
private void visibleLoadEnd(BaseViewHolder holder, boolean visible) {
final int loadEndViewId = getLoadEndViewId();
if (loadEndViewId != 0) {
holder.setVisible(loadEndViewId, visible);
}
}
public final void setLoadMoreEndGone(boolean loadMoreEndGone) {
this.mLoadMoreEndGone = loadMoreEndGone;
}
public final boolean isLoadEndMoreGone() {
if (getLoadEndViewId() == 0) {
return true;
}
return mLoadMoreEndGone;
}
/**
* No more data is hidden
*
* @return true for no more data hidden load more
* @deprecated
*/
@Deprecated
public boolean isLoadEndGone() {
return mLoadMoreEndGone;
}
/**
* load more layout
*
* @return
*/
public abstract
@LayoutRes
int getLayoutId();
/**
* loading view
*
* @return
*/
protected abstract
@IdRes
int getLoadingViewId();
/**
* load fail view
*
* @return
*/
protected abstract
@IdRes
int getLoadFailViewId();
/**
* load end view, you can return 0
*
* @return
*/
protected abstract
@IdRes
int getLoadEndViewId();
}
package com.yidianling.avchatkit.common.recyclerview.loadmore;
import com.yidianling.im.R;
public final class SimpleLoadMoreView extends LoadMoreView {
@Override
public int getLayoutId() {
return R.layout.nim_simple_load_more;
}
@Override
protected int getLoadingViewId() {
return R.id.load_more_loading_view;
}
@Override
protected int getLoadFailViewId() {
return R.id.load_more_load_fail_view;
}
@Override
protected int getLoadEndViewId() {
return R.id.load_more_load_end_view;
}
}
package com.yidianling.avchatkit.common.recyclerview.util;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SimpleItemAnimator;
/**
* Created by hzxuwen on 2017/1/13.
*/
public class RecyclerViewUtil {
public static void changeItemAnimation(RecyclerView recyclerView, boolean isOpen) {
// 关闭viewholder动画效果。解决viewholder闪烁问题
RecyclerView.ItemAnimator animator = recyclerView.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
((SimpleItemAnimator) animator).setSupportsChangeAnimations(isOpen);
}
}
}
package com.yidianling.avchatkit.common.util;
import android.text.TextUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
/**
* 反射工具类
* 通过反射获得对应函数功能
*/
public class ReflectionUtil {
/**
* 调用Class的静态方法
*/
public static <T> T invokeClassMethod(String classPath, String methodName, Class[] paramClasses, Object[] params) {
return (T) executeClassLoad(getClass(classPath), methodName, paramClasses, params);
}
/**
* 调用Class的无参静态方法
*/
public static Object invokeMethod(Object obj, String methodName, Object[] params) {
return invokeMethod(obj, methodName, null, params);
}
/**
* 通过类对象,运行指定方法
*
* @param obj 类对象
* @param methodName 方法名
* @param paramTypes 参数对应的类型(如果不指定,那么从params来判断,可能会判断不准确,例如把CharSequence 判断成String,导致反射时方法找不到)
* @param params 参数值
* @return 失败返回null
*/
public static Object invokeMethod(Object obj, String methodName, Class<?>[] paramTypes, Object[] params) {
if (obj == null || TextUtils.isEmpty(methodName)) {
return null;
}
Class<?> clazz = obj.getClass();
try {
if (paramTypes == null) {
if (params != null) {
paramTypes = new Class[params.length];
for (int i = 0; i < params.length; ++i) {
paramTypes[i] = params[i].getClass();
}
}
}
Method method = clazz.getMethod(methodName, paramTypes);
method.setAccessible(true);
return method.invoke(obj, params);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 反射获取对象属性值
*/
public static Object getFieldValue(Object obj, String fieldName) {
if (obj == null || TextUtils.isEmpty(fieldName)) {
return null;
}
Class<?> clazz = obj.getClass();
while (clazz != Object.class) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
} catch (Exception e) {
e.printStackTrace();
}
clazz = clazz.getSuperclass();
}
return null;
}
/**
* 反射修改对象属性值
*/
public static void setFieldValue(Object obj, String fieldName, Object value) {
if (obj == null || TextUtils.isEmpty(fieldName)) {
return;
}
Class<?> clazz = obj.getClass();
while (clazz != Object.class) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
return;
} catch (Exception e) {
e.printStackTrace();
}
clazz = clazz.getSuperclass();
}
}
/**
* 获取Class所有的静态成员
*/
public static List<Field> getClassStaticField(Class clazz) {
Field[] fields = clazz.getFields();
List<Field> ret = new ArrayList<>();
for (Field field : fields) {
String m = Modifier.toString(field.getModifiers());
if (m.contains("static")) {
ret.add(field);
}
}
return ret;
}
/**
* 获取Class所有静态字段的值
*/
public static List<Object> getClassStaticFieldValue(Class clazz) {
Field[] fields = clazz.getFields();
List<Object> ret = new ArrayList<>();
for (Field field : fields) {
String m = Modifier.toString(field.getModifiers());
if (m.contains("static")) {
try {
ret.add(field.get(null));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return ret;
}
/**
* 获取某个字段的@annotation
*/
public static <A extends Annotation> A getFieldAnnotationsByType(Field field, Class<A> annotationType) {
return field.getAnnotation(annotationType);
}
/**
* 判断某个类是否包含某个方法,前提是这个类也存在
*/
public static boolean hasMethod(String classPath, String methodName, Class[] paramClasses) {
return getMethod(getClass(classPath), methodName, paramClasses) != null;
}
/**
* 反射修改类的私有静态final变量(常量)的值
* 注意:对于基本类型的静态常量,JAVA在编译的时候就会把代码中对此常量中引用的地方替换成相应常量值。
* 结论:反射失效的情会发生在int、long、boolean以及String这些基本类型上,而如果把类型改成Integer、Long、Boolean这种包装类型,或者其他诸如Date、Object都不会出现失效的情况。
*/
public static void setFinalStaticField(final Class clazz, final String fieldName, final Object newValue) {
try {
// 获取要反射的类字段
Field field = clazz.getDeclaredField(fieldName);
// 打开字段的访问权权限,去除private修饰符的影响
field.setAccessible(true);
// 去除final修饰符的影响,将字段设为可修改的
// Field modifiersField = Field.class.getDeclaredField("modifiers");
// modifiersField.setAccessible(true);
// modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
// 设置新的值
field.set(null, newValue);
} catch (Throwable th) {
th.printStackTrace();
}
}
/**
* ****************************** basic ******************************
*/
private static Class getClass(String str) {
Class cls = null;
try {
cls = Class.forName(str);
} catch (ClassNotFoundException ignored) {
ignored.printStackTrace();
}
return cls;
}
private static Object executeClassLoad(Class cls, String str, Class[] clsArr, Object[] objArr) {
Object obj = null;
if (!(cls == null || checkObjExists(str))) {
Method method = getMethod(cls, str, clsArr);
if (method != null) {
method.setAccessible(true);
try {
obj = method.invoke(null, objArr);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
return obj;
}
private static Method getMethod(Class cls, String str, Class[] clsArr) {
Method method = null;
if (cls == null || checkObjExists(str)) {
return null;
}
try {
cls.getMethods();
cls.getDeclaredMethods();
return cls.getDeclaredMethod(str, clsArr);
} catch (Exception e) {
try {
return cls.getMethod(str, clsArr);
} catch (Exception e2) {
return cls.getSuperclass() != null ? getMethod(cls.getSuperclass(), str, clsArr) : method;
}
}
}
private static boolean checkObjExists(Object obj) {
return obj == null || obj.toString().equals("") || obj.toString().trim().equals("null");
}
}
\ No newline at end of file
package com.yidianling.avchatkit.common.util;
import android.content.Context;
import android.content.res.Resources;
import android.util.DisplayMetrics;
import android.util.Log;
import com.yidianling.avchatkit.AVChatKit;
import java.lang.reflect.Field;
public class ScreenUtil {
private static final String TAG = "Demo.ScreenUtil";
private static double RATIO = 0.85;
public static int screenWidth;
public static int screenHeight;
public static int screenMin;// 宽高中,小的一边
public static int screenMax;// 宽高中,较大的值
public static float density;
public static float scaleDensity;
public static float xdpi;
public static float ydpi;
public static int densityDpi;
public static int dialogWidth;
public static int statusbarheight;
public static int navbarheight;
static {
init(AVChatKit.getContext());
}
public static int dip2px(float dipValue) {
return (int) (dipValue * density + 0.5f);
}
public static int px2dip(float pxValue) {
return (int) (pxValue / density + 0.5f);
}
public static int sp2px(float spValue) {
return (int) (spValue * scaleDensity + 0.5f);
}
public static int getDialogWidth() {
dialogWidth = (int) (screenMin * RATIO);
return dialogWidth;
}
public static void init(Context context) {
if (null == context) {
return;
}
DisplayMetrics dm = context.getApplicationContext().getResources().getDisplayMetrics();
screenWidth = dm.widthPixels;
screenHeight = dm.heightPixels;
screenMin = (screenWidth > screenHeight) ? screenHeight : screenWidth;
density = dm.density;
scaleDensity = dm.scaledDensity;
xdpi = dm.xdpi;
ydpi = dm.ydpi;
densityDpi = dm.densityDpi;
Log.d(TAG, "screenWidth=" + screenWidth + " screenHeight=" + screenHeight + " density=" + density);
}
public static int getDisplayWidth() {
if (screenWidth == 0) {
GetInfo(AVChatKit.getContext());
}
return screenWidth;
}
public static int getDisplayHeight() {
if (screenHeight == 0) {
GetInfo(AVChatKit.getContext());
}
return screenHeight;
}
public static void GetInfo(Context context) {
if (null == context) {
return;
}
DisplayMetrics dm = context.getApplicationContext().getResources().getDisplayMetrics();
screenWidth = dm.widthPixels;
screenHeight = dm.heightPixels;
screenMin = (screenWidth > screenHeight) ? screenHeight : screenWidth;
screenMax = (screenWidth < screenHeight) ? screenHeight : screenWidth;
density = dm.density;
scaleDensity = dm.scaledDensity;
xdpi = dm.xdpi;
ydpi = dm.ydpi;
densityDpi = dm.densityDpi;
statusbarheight = getStatusBarHeight(context);
navbarheight = getNavBarHeight(context);
Log.d(TAG, "screenWidth=" + screenWidth + " screenHeight=" + screenHeight + " density=" + density);
}
public static int getStatusBarHeight(Context context) {
if (statusbarheight == 0) {
try {
Class<?> c = Class.forName("com.android.internal.R$dimen");
Object o = c.newInstance();
Field field = c.getField("status_bar_height");
int x = (Integer) field.get(o);
statusbarheight = context.getResources().getDimensionPixelSize(x);
} catch (Exception e) {
e.printStackTrace();
}
}
if (statusbarheight == 0) {
statusbarheight = ScreenUtil.dip2px(25);
}
return statusbarheight;
}
public static int getNavBarHeight(Context context) {
Resources resources = context.getResources();
int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
return resources.getDimensionPixelSize(resourceId);
}
return 0;
}
}
package com.yidianling.avchatkit.common.widgets;
import android.util.Pair;
import android.widget.ImageView;
import android.widget.TextView;
import com.yidianling.im.R;
import com.yidianling.avchatkit.common.adapter.TViewHolder;
public class MultiSelectDialogViewHolder extends TViewHolder {
private TextView textView;
private ImageView imageView;
@Override
protected int getResId() {
return R.layout.multi_select_dialog_list_item;
}
@Override
protected void inflate() {
textView = (TextView) view.findViewById(R.id.select_dialog_text_view);
imageView = (ImageView) view.findViewById(R.id.select_dialog_image_view);
}
@Override
protected void refresh(Object item) {
if (item instanceof Pair<?, ?>) {
Pair<String, Boolean> pair = (Pair<String, Boolean>) item;
textView.setText(pair.first);
imageView.setPressed(pair.second);
}
}
}
package com.yidianling.avchatkit.common.widgets;
import android.view.View;
/**
* Created by hzlichengda on 14-3-31.
*/
public interface ToggleListener {
void toggleOn(View v);
void toggleOff(View v);
void toggleDisable(View v);
}
package com.yidianling.avchatkit.common.widgets;
/**
* Created by hzlichengda on 14-3-31.
*/
public enum ToggleState {
DISABLE, //禁用
OFF, // normal
ON //selected
}
package com.yidianling.avchatkit.common.widgets;
import android.view.View;
import android.view.ViewGroup;
/**
* Created by hzlichengda on 14-3-14.
* if you want to use this parentView with an inner parentView inside ,the inner parentView's id must be R.id.childView
*/
public class ToggleView {
private View parentView = null;
private View childView = null;
private ToggleState state = ToggleState.DISABLE;
private ToggleListener listener = null;
public ToggleView(View parentView, ToggleState initState, ToggleListener listener) {
this.parentView = parentView;
this.state = initState;
this.listener = listener;
init();
}
private void init() {
if (parentView != null) {
parentView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onToggleStateChange();
}
});
if (parentView instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) parentView;
childView = viewGroup.getChildAt(0);
}
toggle(state);
}
}
public void toggle(ToggleState state) {
switch (state) {
case DISABLE:
disable(false);
break;
case OFF:
off(false);
break;
case ON:
on(false);
break;
}
}
private void onToggleStateChange() {
switch (state) {
case DISABLE:
disable(true);
break;
case OFF:
on(true);
break;
case ON:
off(true);
break;
}
}
public void on(boolean callback) {
state = ToggleState.ON;
parentView.setEnabled(true);
parentView.setSelected(true);
if (childView != null) {
childView.setEnabled(true);
childView.setSelected(true);
}
if (listener != null && callback)
listener.toggleOn(parentView);
}
public void off(boolean callback) {
state = ToggleState.OFF;
parentView.setEnabled(true);
parentView.setSelected(false);
if (childView != null) {
childView.setEnabled(true);
childView.setSelected(false);
}
if (listener != null && callback)
listener.toggleOff(parentView);
}
public void disable(boolean callback) {
state = ToggleState.DISABLE;
parentView.setSelected(false);
parentView.setEnabled(false);
if (childView != null) {
childView.setSelected(false);
childView.setEnabled(false);
}
if (listener != null && callback)
listener.toggleDisable(parentView);
}
public void enable() {
off(false);
}
public boolean isEnable() {
return state != ToggleState.DISABLE;
}
}
package com.yidianling.avchatkit.config;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import com.netease.nimlib.sdk.avchat.constant.AVChatAudioEffectMode;
import com.netease.nimlib.sdk.avchat.constant.AVChatMediaCodecMode;
import com.netease.nimlib.sdk.avchat.constant.AVChatUserRole;
import com.netease.nimlib.sdk.avchat.model.AVChatImageFormat;
import com.netease.nimlib.sdk.avchat.model.AVChatParameters;
import com.yidianling.im.R;
/**
* Created by winnie on 2017/12/10.
*/
public class AVChatConfigs {
private AVChatParameters avChatParameters;
private Context context;
//Config from Preference
private int videoCropRatio;
private boolean videoAutoRotate;
private int videoQuality;
private boolean serverRecordAudio;
private boolean serverRecordVideo;
private boolean autoCallProximity;
private int videoHwEncoderMode;
private int videoHwDecoderMode;
private boolean videoFpsReported;
private int audioEffectAecMode;
private int audioEffectNsMode;
private int videoMaxBitrate;
private int deviceDefaultRotation;
private int deviceRotationOffset;
private boolean audioHighQuality;
public AVChatConfigs(Context context) {
this.context = context;
this.avChatParameters = new AVChatParameters();
configFromPreference(PreferenceManager.getDefaultSharedPreferences(context));
updateAVChatOptionalConfig();
}
private void configFromPreference(SharedPreferences preferences) {
videoCropRatio = Integer.parseInt(preferences.getString(context.getString(R.string.nrtc_setting_vie_crop_ratio_key), "0"));
videoAutoRotate = preferences.getBoolean(context.getString(R.string.nrtc_setting_vie_rotation_key), true);
videoQuality = Integer.parseInt(preferences.getString(context.getString(R.string.nrtc_setting_vie_quality_key), 0 + ""));
serverRecordAudio = preferences.getBoolean(context.getString(R.string.nrtc_setting_other_server_record_audio_key), false);
serverRecordVideo = preferences.getBoolean(context.getString(R.string.nrtc_setting_other_server_record_video_key), false);
autoCallProximity = preferences.getBoolean(context.getString(R.string.nrtc_setting_voe_call_proximity_key), true);
videoHwEncoderMode = Integer.parseInt(preferences.getString(context.getString(R.string.nrtc_setting_vie_hw_encoder_key), 0 + ""));
videoHwDecoderMode = Integer.parseInt(preferences.getString(context.getString(R.string.nrtc_setting_vie_hw_decoder_key), 0 + ""));
videoFpsReported = preferences.getBoolean(context.getString(R.string.nrtc_setting_vie_fps_reported_key), true);
audioEffectAecMode = Integer.parseInt(preferences.getString(context.getString(R.string.nrtc_setting_voe_audio_aec_key), 2 + ""));
audioEffectNsMode = Integer.parseInt(preferences.getString(context.getString(R.string.nrtc_setting_voe_audio_ns_key), 2 + ""));
String value1 = preferences.getString(context.getString(R.string.nrtc_setting_vie_max_bitrate_key), 0 + "");
videoMaxBitrate = Integer.parseInt(TextUtils.isDigitsOnly(value1) && !TextUtils.isEmpty(value1) ? value1 : 0 + "");
String value2 = preferences.getString(context.getString(R.string.nrtc_setting_other_device_default_rotation_key), 0 + "");
deviceDefaultRotation = Integer.parseInt(TextUtils.isDigitsOnly(value2) && !TextUtils.isEmpty(value2) ? value2 : 0 + "");
String value3 = preferences.getString(context.getString(R.string.nrtc_setting_other_device_rotation_fixed_offset_key), 0 + "");
deviceRotationOffset = Integer.parseInt(TextUtils.isDigitsOnly(value3) && !TextUtils.isEmpty(value3) ? value3 : 0 + "");
audioHighQuality = preferences.getBoolean(context.getString(R.string.nrtc_setting_voe_high_quality_key), false);
}
/**
* 1, autoCallProximity: 语音通话时使用, 距离感应自动黑屏
* 2, videoCropRatio: 制定固定的画面裁剪比例,发送端有效
* 3, videoAutoRotate: 结合自己设备角度和对方设备角度自动旋转画面
* 4, serverRecordAudio: 需要服务器录制语音, 同时需要 APP KEY 下面开通了服务器录制功能
* 5, serverRecordVideo: 需要服务器录制视频, 同时需要 APP KEY 下面开通了服务器录制功能
* 7, videoQuality: 视频质量调整, 最高建议使用480P
* 8, videoFpsReported: 是否开启视频绘制帧率汇报
* 9, deviceDefaultRotation: 99.99%情况下你不需要设置这个参数, 当设备固定在水平方向时,并且设备不会移动, 这时是无法确定设备角度的,可以设置一个默认角度
* 10, deviceRotationOffset: 99.99%情况下你不需要设置这个参数, 当你的设备传感器获取的角度永远偏移固定值时设置,用于修正旋转角度
* 11, videoMaxBitrate: 视频最大码率设置, 100K ~ 5M. 如果没有特殊需求不要去设置,会影响SDK内部的调节机制
* 12, audioEffectAecMode: 语音处理选择, 默认使用平台内置,当你发现平台内置不好用时可以设置到SDK内置
* 13, audioEffectNsMode: 语音处理选择, 默认使用平台内置,当你发现平台内置不好用时可以设置到SDK内置
* 14, videoHwEncoderMode: 视频编码类型, 默认情况下不用设置.
* 15, videoHwDecoderMode: 视频解码类型, 默认情况下不用设置.
* 16, audioHighQuality: 高清语音,采用更高的采样率来传输语音
* 17, audioDtx: 非连续发送,当监测到人声非活跃状态时减少数据包的发送
*/
private void updateAVChatOptionalConfig() {
avChatParameters.setBoolean(AVChatParameters.KEY_AUDIO_CALL_PROXIMITY, autoCallProximity);
avChatParameters.setInteger(AVChatParameters.KEY_VIDEO_FIXED_CROP_RATIO, videoCropRatio);
avChatParameters.setBoolean(AVChatParameters.KEY_VIDEO_ROTATE_IN_RENDING, videoAutoRotate);
// avChatParameters.setBoolean(AVChatParameters.KEY_SERVER_AUDIO_RECORD, serverRecordAudio);
// avChatParameters.setBoolean(AVChatParameters.KEY_SERVER_VIDEO_RECORD, serverRecordVideo);
avChatParameters.setInteger(AVChatParameters.KEY_VIDEO_QUALITY, videoQuality);
avChatParameters.setBoolean(AVChatParameters.KEY_VIDEO_FPS_REPORTED, videoFpsReported);
avChatParameters.setInteger(AVChatParameters.KEY_DEVICE_DEFAULT_ROTATION, deviceDefaultRotation);
avChatParameters.setInteger(AVChatParameters.KEY_DEVICE_ROTATION_FIXED_OFFSET, deviceRotationOffset);
if (videoMaxBitrate > 0) {
avChatParameters.setInteger(AVChatParameters.KEY_VIDEO_MAX_BITRATE, videoMaxBitrate * 1024);
}
switch (audioEffectAecMode) {
case 0:
avChatParameters.setString(AVChatParameters.KEY_AUDIO_EFFECT_ACOUSTIC_ECHO_CANCELER, AVChatAudioEffectMode.DISABLE);
break;
case 1:
avChatParameters.setString(AVChatParameters.KEY_AUDIO_EFFECT_ACOUSTIC_ECHO_CANCELER, AVChatAudioEffectMode.SDK_BUILTIN);
break;
case 2:
avChatParameters.setString(AVChatParameters.KEY_AUDIO_EFFECT_ACOUSTIC_ECHO_CANCELER, AVChatAudioEffectMode.PLATFORM_BUILTIN);
break;
}
switch (audioEffectNsMode) {
case 0:
avChatParameters.setString(AVChatParameters.KEY_AUDIO_EFFECT_NOISE_SUPPRESSOR, AVChatAudioEffectMode.DISABLE);
break;
case 1:
avChatParameters.setString(AVChatParameters.KEY_AUDIO_EFFECT_NOISE_SUPPRESSOR, AVChatAudioEffectMode.SDK_BUILTIN);
break;
case 2:
avChatParameters.setString(AVChatParameters.KEY_AUDIO_EFFECT_NOISE_SUPPRESSOR, AVChatAudioEffectMode.PLATFORM_BUILTIN);
break;
}
switch (videoHwEncoderMode) {
case 0:
avChatParameters.setString(AVChatParameters.KEY_VIDEO_ENCODER_MODE, AVChatMediaCodecMode.MEDIA_CODEC_AUTO);
break;
case 1:
avChatParameters.setString(AVChatParameters.KEY_VIDEO_ENCODER_MODE, AVChatMediaCodecMode.MEDIA_CODEC_SOFTWARE);
break;
case 2:
avChatParameters.setString(AVChatParameters.KEY_VIDEO_ENCODER_MODE, AVChatMediaCodecMode.MEDIA_CODEC_HARDWARE);
break;
}
switch (videoHwDecoderMode) {
case 0:
avChatParameters.setString(AVChatParameters.KEY_VIDEO_DECODER_MODE, AVChatMediaCodecMode.MEDIA_CODEC_AUTO);
break;
case 1:
avChatParameters.setString(AVChatParameters.KEY_VIDEO_DECODER_MODE, AVChatMediaCodecMode.MEDIA_CODEC_SOFTWARE);
break;
case 2:
avChatParameters.setString(AVChatParameters.KEY_VIDEO_DECODER_MODE, AVChatMediaCodecMode.MEDIA_CODEC_HARDWARE);
break;
}
avChatParameters.setBoolean(AVChatParameters.KEY_AUDIO_HIGH_QUALITY, audioHighQuality);
//观众角色,多人模式下使用. IM Demo没有多人通话, 全部设置为AVChatUserRole.NORMAL.
avChatParameters.setInteger(AVChatParameters.KEY_SESSION_MULTI_MODE_USER_ROLE, AVChatUserRole.NORMAL);
//采用I420图像格式
avChatParameters.setInteger(AVChatParameters.KEY_VIDEO_FRAME_FILTER_FORMAT, AVChatImageFormat.I420);
}
public AVChatParameters getAvChatParameters() {
return avChatParameters;
}
public boolean isServerRecordAudio() {
return serverRecordAudio;
}
public boolean isServerRecordVideo() {
return serverRecordVideo;
}
}
package com.yidianling.avchatkit.config;
import android.app.Activity;
import android.content.Context;
/**
* 音视频初始化配置
* Created by winnie on 2017/12/7.
*/
public class AVChatOptions {
/**
* 通知入口
*/
public Class<? extends Activity> entranceActivity;
/**
* 通知栏icon
*/
public int notificationIconRes;
/**
* 被踢出时,调用的方法
*/
public void logout(Context context){
}
}
package com.yidianling.avchatkit.config;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import com.netease.nimlib.sdk.avchat.model.AVChatServerAddresses;
import org.json.JSONException;
import org.json.JSONObject;
/**
* 网易云信音视频私有化配置项
*/
public class AVPrivatizationConfig {
private static final String KEY_NRTC_SERVER = "nrtc_server";
private static final String KEY_NRTC_ROOMSERVER = "nrtc_roomserver";
private static final String KEY_KIBANA_SERVER = "kibana_server";
private static final String KEY_STATISTIC_SERVER = "statistic_server";
private static final String KEY_NETDETECT_SERVER = "netdetect_server";
private static final String KEY_COMPAT_SERVER = "compat_server";
private static final String SHARE_NAME = "nim_demo_private_config";
private static final String KEY_CONFIG_ENABLE = "private_config_enable";
private static final String KEY_CONFIG_JSON = "private_config_json";
public static AVChatServerAddresses getServerAddresses(Context context) {
/**
* KEY_CONFIG_JSON 来源于IM demo 的私有化配置 , 参考DemoPrivatizationConfig
*/
String configStr = getSP(context).getString(KEY_CONFIG_JSON, null);
if (TextUtils.isEmpty(configStr)) {
return null;
}
if (isPrivateDisable(context)) {
return null;
}
AVChatServerAddresses rtcServerAddresses = null;
try {
JSONObject jsonObject = new JSONObject(configStr);
rtcServerAddresses = new AVChatServerAddresses();
// rtcServerAddresses.channelServer = jsonObject.optString(KEY_NRTC_SERVER,null);
rtcServerAddresses.roomServer = jsonObject.optString(KEY_NRTC_ROOMSERVER, null);
rtcServerAddresses.statisticsServer = jsonObject.optString(KEY_KIBANA_SERVER, null);
rtcServerAddresses.functionServer = jsonObject.optString(KEY_STATISTIC_SERVER, null);
rtcServerAddresses.netDetectServer = jsonObject.optString(KEY_NETDETECT_SERVER, null);
rtcServerAddresses.compatServer = jsonObject.optString(KEY_COMPAT_SERVER, null);
} catch (JSONException e) {
e.printStackTrace();
}
return rtcServerAddresses;
}
private static boolean isPrivateDisable(Context context) {
return !getSP(context).getBoolean(KEY_CONFIG_ENABLE, false);
}
private static SharedPreferences getSP(Context context) {
return context.getSharedPreferences(SHARE_NAME, Context.MODE_PRIVATE);
}
}
package com.yidianling.avchatkit.constant;
/**
* Created by hzxuwen on 2015/4/24.
*/
public class AVChatExitCode {
public static final int PEER_HANGUP = 0;
public static final int PEER_REJECT = 1;
public static final int HANGUP = 2;
public static final int NET_CHANGE = 4;
public static final int REJECT = 5;
public static final int PEER_BUSY = 6;
public static final int NET_ERROR = 8;
public static final int KICKED_OUT = 9;
public static final int CONFIG_ERROR = 10;
public static final int PROTOCOL_INCOMPATIBLE_SELF_LOWER = 12;
public static final int PROTOCOL_INCOMPATIBLE_PEER_LOWER = 13;
public static final int INVALIDE_CHANNELID = 14;
public static final int OPEN_DEVICE_ERROR = 15;
public static final int SYNC_REJECT = 16;
public static final int SYNC_ACCEPT = 17;
public static final int SYNC_HANGUP = 18;
public static final int PEER_NO_RESPONSE = 19; //超时,无人接听
public static final int CANCEL = 20; //取消
public static final int LOCAL_CALL_BUSY = 21; // 正在进行本地通话
public static String getExitString(int code) {
switch (code) {
case PEER_HANGUP:
return "PEER_HANGUP";
case PEER_REJECT:
return "PEER_REJECT";
case HANGUP:
return "HANGUP";
case NET_CHANGE:
return "NET_CHANGE";
case REJECT:
return "REJECT";
case PEER_BUSY:
return "PEER_BUSY";
case NET_ERROR:
return "NET_ERROR";
case KICKED_OUT:
return "KICKED_OUT";
case CONFIG_ERROR:
return "CONFIG_ERROR";
case PROTOCOL_INCOMPATIBLE_SELF_LOWER:
return "PROTOCOL_INCOMPATIBLE_SELF_LOWER";
case PROTOCOL_INCOMPATIBLE_PEER_LOWER:
return "PROTOCOL_INCOMPATIBLE_PEER_LOWER";
case INVALIDE_CHANNELID:
return "INVALIDE_CHANNELID";
case OPEN_DEVICE_ERROR:
return "OPEN_DEVICE_ERROR";
case SYNC_REJECT:
return "SYNC_REJECT";
case SYNC_ACCEPT:
return "SYNC_ACCEPT";
case SYNC_HANGUP:
return "SYNC_HANGUP";
case CANCEL:
return "CANCEL";
case PEER_NO_RESPONSE:
return "PEER_NO_RESPONSE";
case LOCAL_CALL_BUSY:
return "LOCAL_CALL_BUSY";
default:
return "UNKNOWN";
}
}
}
package com.yidianling.avchatkit.constant;
/**
* Created by winnie on 2017/12/7.
*/
public class AVChatExtras {
public static String EXTRA_FROM_NOTIFICATION = "from_notification";
// 参数
public static String EXTRA_ACCOUNT = "account";
}
package com.yidianling.avchatkit.constant;
/**
* 呼叫状态,用来控制刷新界面
* Created by hzxuwen on 2015/4/27.
*/
public enum CallStateEnum {
INVALID(-1), //无效的状态,该状态下无界面显示
VIDEO(0), //正在进行视频通话(发起者)
OUTGOING_VIDEO_CALLING(2), //邀请好友视频通话
INCOMING_VIDEO_CALLING(4),
OUTGOING_AUDIO_TO_VIDEO(6), //向好友发起从语音切换到视频的邀请
VIDEO_CONNECTING(8), //视频通话连接中
VIDEO_OFF(10), // 对方关闭摄像头
AUDIO(1), //正在进行语音通话(发起者)
OUTGOING_AUDIO_CALLING(3), //邀请好友语音通话
INCOMING_AUDIO_CALLING(5), //来自好友的视频通话、语音通话邀请
INCOMING_AUDIO_TO_VIDEO(7), //音频切换为视频的邀请
AUDIO_CONNECTING(9); //语音通话连接中
private int value;
CallStateEnum(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static boolean isVideoMode(CallStateEnum value) {
return value.getValue() % 2 == 0;
}
public static boolean isAudioMode(CallStateEnum value) {
return value.getValue() % 2 == 1;
}
public static CallStateEnum getCallStateEnum(int value) {
for (CallStateEnum e : values()) {
if (e.getValue() == value) {
return e;
}
}
return INVALID;
}
}
package com.yidianling.avchatkit.controll;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.SoundPool;
import com.yidianling.avchatkit.AVChatKit;
import com.yidianling.im.R;
import com.yidianling.avchatkit.common.log.LogUtil;
/**
* SoundPool 铃声尽量不要超过1M
* 在不同的系统下 SoundPool 表现可能存在不一致
*/
public class AVChatSoundPlayer {
private static final String TAG = "AVChatSoundPlayer";
public enum RingerTypeEnum {
CONNECTING,
NO_RESPONSE,
PEER_BUSY,
PEER_REJECT,
RING,;
}
private Context context;
private SoundPool soundPool;
private AudioManager audioManager;
private int streamId;
private int soundId;
private boolean loop;
private RingerTypeEnum ringerTypeEnum;
private boolean isRingModeRegister = false;
private int ringMode = -1;
private static AVChatSoundPlayer instance = null;
private RingModeChangeReceiver ringModeChangeReceiver;
public static AVChatSoundPlayer instance() {
if (instance == null) {
synchronized (AVChatSoundPlayer.class) {
if (instance == null) {
instance = new AVChatSoundPlayer();
}
}
}
return instance;
}
public AVChatSoundPlayer() {
this.context = AVChatKit.getContext();
}
public synchronized void play(RingerTypeEnum type) {
LogUtil.d(TAG, "play type->" + type.name());
this.ringerTypeEnum = type;
int ringId = 0;
switch (type) {
case NO_RESPONSE:
ringId = R.raw.avchat_no_response;
loop = false;
break;
case PEER_BUSY:
ringId = R.raw.avchat_peer_busy;
loop = false;
break;
case PEER_REJECT:
ringId = R.raw.avchat_peer_reject;
loop = false;
break;
case CONNECTING:
ringId = R.raw.avchat_connecting;
loop = true;
break;
case RING:
ringId = R.raw.avchat_ring;
loop = true;
break;
}
if (ringId != 0) {
play(ringId);
}
}
public void stop() {
LogUtil.d(TAG, "stop");
if (soundPool != null) {
if (streamId != 0) {
soundPool.stop(streamId);
streamId = 0;
}
if (soundId != 0) {
soundPool.unload(soundId);
soundId = 0;
}
}
if (isRingModeRegister) {
registerVolumeReceiver(false);
}
}
private void play(int ringId) {
initSoundPool();
if (audioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL) {
soundId = soundPool.load(context, ringId, 1);
}
}
private void initSoundPool() {
stop();
if (soundPool == null) {
soundPool = new SoundPool(1, AudioManager.STREAM_RING, 0);
soundPool.setOnLoadCompleteListener(onLoadCompleteListener);
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
ringMode = audioManager.getRingerMode();
}
registerVolumeReceiver(true);
}
SoundPool.OnLoadCompleteListener onLoadCompleteListener = new SoundPool.OnLoadCompleteListener() {
@Override
public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
if (soundId != 0 && status == 0) {
if (audioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL) {
int curVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING);
streamId = soundPool.play(soundId, curVolume, curVolume, 1, loop ? -1 : 0, 1f);
}
}
}
};
private void registerVolumeReceiver(boolean register) {
if (ringModeChangeReceiver == null) {
ringModeChangeReceiver = new RingModeChangeReceiver();
}
if (register) {
isRingModeRegister = true;
IntentFilter filter = new IntentFilter();
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
context.registerReceiver(ringModeChangeReceiver, filter);
} else {
context.unregisterReceiver(ringModeChangeReceiver);
isRingModeRegister = false;
}
}
private class RingModeChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (ringMode != -1 && ringMode != audioManager.getRingerMode()
&& intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
ringMode = audioManager.getRingerMode();
play(ringerTypeEnum);
}
}
}
}
package com.yidianling.avchatkit.model;
/**
* 群组相关数据提供者
* Created by winnie on 2017/12/25.
*/
public abstract class ITeamDataProvider {
/**
* 获取显示名称。
*/
public abstract String getDisplayNameWithoutMe(String teamId, String account);
/**
* 获取显示名称。用户本人显示“我”
*/
public abstract String getTeamMemberDisplayName(String teamId, String account);
}
package com.yidianling.avchatkit.model;
import com.netease.nimlib.sdk.uinfo.model.UserInfo;
/**
* 用户相关资料提供者
* Created by winnie on 2017/12/19.
*/
public abstract class IUserInfoProvider {
/**
* 获取用户资料
* @param account 用户账号
* @return UserInfo 用户资料
*/
public abstract UserInfo getUserInfo(String account);
/**
* 获取用户显示名称
* @param account 用户账号
* @return 用户显示名称
*/
public abstract String getUserDisplayName(String account);
}
package com.yidianling.avchatkit.module;
/**
* Created by winnie on 2017/12/10.
*/
public interface AVChatControllerCallback<T> {
void onSuccess(T t);
void onFailed(int code, String errorMsg);
}
package com.yidianling.avchatkit.module;
import android.os.Handler;
import com.netease.nimlib.sdk.Observer;
import com.yidianling.avchatkit.AVChatKit;
import com.yidianling.avchatkit.common.log.LogUtil;
import java.util.ArrayList;
import java.util.List;
/**
* 音视频通话时超时监听处理
* Created by weilv on 17/9/11.
*/
public class AVChatTimeoutObserver {
private static final String TAG = "AVChatTimeoutObserver";
private List<TimeoutObserver> timeoutObservers = new ArrayList<>();
private List<Observer<Integer>> timeoutObserverLocal = new ArrayList<>(1); // 来电or呼出超时监听
private Handler uiHandler;
private final int OUTGOING_TIME_OUT = 45 * 1000;
private final int INCOMING_TIME_OUT = 55 * 1000;
private static class InstanceHolder {
public final static AVChatTimeoutObserver instance = new AVChatTimeoutObserver();
}
public static AVChatTimeoutObserver getInstance() {
return InstanceHolder.instance;
}
private AVChatTimeoutObserver() {
uiHandler = new Handler(AVChatKit.getContext().getMainLooper());
}
// 通知APP观察者
private <T> void notifyObservers(List<Observer<T>> observers, T result) {
if (observers == null || observers.isEmpty()) {
return;
}
// 创建副本,为了使得回调到app后,app如果立即注销观察者,会造成List异常。
List<Observer<T>> copy = new ArrayList<>(observers.size());
copy.addAll(observers);
for (Observer<T> o : copy) {
o.onEvent(result);
}
}
// 注册注销APP观察者
private <T> void registerObservers(List<Observer<T>> observers, final Observer<T> observer, boolean register) {
if (observers == null || observer == null) {
return;
}
if (register) {
observers.add(observer);
} else {
observers.remove(observer);
}
}
public void observeTimeoutNotification(Observer<Integer> observer, boolean register, boolean isIncoming) {
LogUtil.i(TAG, "observeTimeoutNotification->" + observer + "#" + register);
registerObservers(timeoutObserverLocal, observer, register);
if (register) {
if (isIncoming) {
addIncomingTimeout();
} else {
addOutgoingTimeout();
}
} else {
removeAllTimeout();
}
}
private class TimeoutObserver implements Runnable {
TimeoutObserver() {
}
@Override
public void run() {
LogUtil.i(TAG, "notify timeout ");
notifyObservers(timeoutObserverLocal, 0);
}
}
private void addOutgoingTimeout() {
TimeoutObserver timeoutObserver = new TimeoutObserver();
timeoutObservers.add(timeoutObserver);
uiHandler.postDelayed(timeoutObserver, OUTGOING_TIME_OUT);
}
private void removeAllTimeout() {
LogUtil.i(TAG, "remove all timeout");
for (TimeoutObserver observer : timeoutObservers) {
uiHandler.removeCallbacks(observer);
}
timeoutObservers.clear();
}
private void addIncomingTimeout() {
TimeoutObserver timeoutObserver = new TimeoutObserver();
timeoutObservers.add(timeoutObserver);
uiHandler.postDelayed(timeoutObserver, INCOMING_TIME_OUT);
}
}
package com.yidianling.avchatkit.module;
/**
* 音视频切换接口
* Created by winnie on 2017/12/12.
*/
public interface AVSwitchListener {
/**
* 视频切换为音频
*/
void onVideoToAudio();
/**
* 音频切换为视频
*/
void onAudioToVideo();
/**
* 同意将音频切换为视频
*/
void onReceiveAudioToVideoAgree();
}
package com.yidianling.avchatkit.module;
import com.netease.nimlib.sdk.avchat.AVChatStateObserver;
import com.netease.nimlib.sdk.avchat.model.AVChatAudioFrame;
import com.netease.nimlib.sdk.avchat.model.AVChatNetworkStats;
import com.netease.nimlib.sdk.avchat.model.AVChatSessionStats;
import com.netease.nimlib.sdk.avchat.model.AVChatVideoFrame;
import java.util.Map;
import java.util.Set;
/**
* Created by winnie on 2017/12/8.
*/
public class SimpleAVChatStateObserver implements AVChatStateObserver {
@Override
public void onTakeSnapshotResult(String account, boolean success, String file) {
}
@Override
public void onAVRecordingCompletion(String account, String filePath) {
}
@Override
public void onAudioRecordingCompletion(String filePath) {
}
@Override
public void onLowStorageSpaceWarning(long availableSize) {
}
@Override
public void onAudioMixingProgressUpdated(long progressMs, long durationMs) {
}
@Override
public void onAudioMixingEvent(int event) {
}
@Override
public void onAudioEffectPreload(int effectId, int result) {
}
@Override
public void onAudioEffectPlayEvent(int effectId, int event) {
}
@Override
public void onJoinedChannel(int code, String audioFile, String videoFile, int elapsed) {
}
@Override
public void onUserJoined(String account) {
}
@Override
public void onUserLeave(String account, int event) {
}
@Override
public void onLeaveChannel() {
}
@Override
public void onProtocolIncompatible(int status) {
}
@Override
public void onDisconnectServer(int code) {
}
@Override
public void onNetworkQuality(String user, int quality, AVChatNetworkStats stats) {
}
@Override
public void onCallEstablished() {
}
@Override
public void onDeviceEvent(int code, String desc) {
}
@Override
public void onConnectionTypeChanged(int netType) {
}
@Override
public void onFirstVideoFrameAvailable(String account) {
}
@Override
public void onFirstVideoFrameRendered(String user) {
}
@Override
public void onVideoFrameResolutionChanged(String user, int width, int height, int rotate) {
}
@Override
public void onVideoFpsReported(String account, int fps) {
}
@Override
public boolean onVideoFrameFilter(AVChatVideoFrame frame, boolean maybeDualInput) {
return false;
}
@Override
public boolean onAudioFrameFilter(AVChatAudioFrame frame) {
return false;
}
@Override
public void onAudioDeviceChanged(int device, Set<Integer> set, boolean shouldSelect) {
}
@Override
public void onReportSpeaker(Map<String, Integer> speakers, int mixedEnergy) {
}
@Override
public void onSessionStats(AVChatSessionStats sessionStats) {
}
@Override
public void onLiveEvent(int event) {
}
}
package com.yidianling.avchatkit.notification;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import com.yidianling.avchatkit.AVChatKit;
import com.yidianling.im.R;
import com.yidianling.avchatkit.activity.AVChatActivity;
import com.yidianling.avchatkit.constant.AVChatExtras;
/**
* 音视频聊天通知栏
* Created by huangjun on 2015/5/14.
*/
public class AVChatNotification {
private Context context;
private NotificationManager notificationManager;
private Notification callingNotification;
private Notification missCallNotification;
private String account;
private String displayName;
private static final int CALLING_NOTIFY_ID = 111;
private static final int MISS_CALL_NOTIFY_ID = 112;
public AVChatNotification(Context context) {
this.context = context;
}
public void init(String account, String displayName) {
this.account = account;
this.displayName = displayName;
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
AVChatNotificationChannelCompat26.createNIMMessageNotificationChannel(context);
}
private void buildCallingNotification() {
if (callingNotification == null) {
Intent localIntent = new Intent();
localIntent.setClass(context, AVChatActivity.class);
localIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
String tickerText = String.format(context.getString(R.string.avchat_notification), displayName);
int iconId = AVChatKit.getAvChatOptions().notificationIconRes;
PendingIntent pendingIntent = PendingIntent.getActivity(context, CALLING_NOTIFY_ID, localIntent, PendingIntent
.FLAG_UPDATE_CURRENT);
callingNotification = makeNotification(pendingIntent, context.getString(R.string.avchat_call), tickerText, tickerText,
iconId, false, false);
}
}
private void buildMissCallNotification() {
if (missCallNotification == null) {
Intent notifyIntent = new Intent(context, AVChatKit.getAvChatOptions().entranceActivity);
notifyIntent.putExtra(AVChatExtras.EXTRA_ACCOUNT, account);
notifyIntent.putExtra(AVChatExtras.EXTRA_FROM_NOTIFICATION, true);
notifyIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
notifyIntent.setAction(Intent.ACTION_VIEW);
notifyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(context, CALLING_NOTIFY_ID, notifyIntent, PendingIntent
.FLAG_UPDATE_CURRENT);
String title = context.getString(R.string.avchat_no_pickup_call);
String tickerText = displayName + ": 【网络通话】";
int iconId = R.drawable.avchat_no_pickup;
missCallNotification = makeNotification(pendingIntent, title, tickerText, tickerText, iconId, true, true);
}
}
private Notification makeNotification(PendingIntent pendingIntent, String title, String content, String tickerText,
int iconId, boolean ring, boolean vibrate) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, AVChatNotificationChannelCompat26.getNIMChannelId(context));
builder.setContentTitle(title)
.setContentText(content)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.setTicker(tickerText)
.setSmallIcon(iconId);
int defaults = Notification.DEFAULT_LIGHTS;
if (vibrate) {
defaults |= Notification.DEFAULT_VIBRATE;
}
if (ring) {
defaults |= Notification.DEFAULT_SOUND;
}
builder.setDefaults(defaults);
return builder.build();
}
public void activeCallingNotification(boolean active) {
if (notificationManager != null) {
if (active) {
buildCallingNotification();
notificationManager.notify(CALLING_NOTIFY_ID, callingNotification);
AVChatKit.getNotifications().put(CALLING_NOTIFY_ID, callingNotification);
} else {
notificationManager.cancel(CALLING_NOTIFY_ID);
AVChatKit.getNotifications().remove(CALLING_NOTIFY_ID);
}
}
}
public void activeMissCallNotification(boolean active) {
if (notificationManager != null) {
if (active) {
buildMissCallNotification();
notificationManager.notify(MISS_CALL_NOTIFY_ID, missCallNotification);
AVChatKit.getNotifications().put(MISS_CALL_NOTIFY_ID, callingNotification);
} else {
notificationManager.cancel(MISS_CALL_NOTIFY_ID);
AVChatKit.getNotifications().remove(MISS_CALL_NOTIFY_ID);
}
}
}
}
package com.yidianling.avchatkit.notification;
import android.annotation.TargetApi;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
import java.util.Locale;
/**
* 适配Android O版本的通知栏
* 采用
* <p>
* Created by huangjun on 2017/11/8.
*/
class AVChatNotificationChannelCompat26 {
private static final String NIM_CHANNEL_ID = "nim_avchat_tip_channel_001";
private static String NIM_CHANNEL_NAME = "AV chat tip channel";
private static String NIM_CHANNEL_DESC = "AV chat tip notification";
static String getNIMChannelId(Context context) {
/*
* 适配关键:target 8.0+必须设置一个channel,8.0以下一定要返回null!否则通知栏弹不出
*/
return isBuildAndTargetO(context) ? NIM_CHANNEL_ID : null;
}
static void createNIMMessageNotificationChannel(Context context) {
/*
* 适配关键:只有8.0+的机器才能创建NotificationChannel,否则会找不到类。target 8.0+才需要去创建一个channel,否则就用默认通道即null
*/
if (!isBuildAndTargetO(context)) {
return;
}
configLanguage(context);
NotificationChannel channel;
NotificationManager manager = ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE));
if (manager != null) {
channel = manager.getNotificationChannel(NIM_CHANNEL_ID); // 已经存在就不要再创建了,无法修改通道配置
if (channel == null) {
channel = buildNIMMessageChannel();
manager.createNotificationChannel(channel);
}
}
}
@TargetApi(Build.VERSION_CODES.O)
private static NotificationChannel buildNIMMessageChannel() {
NotificationChannel channel = new NotificationChannel(NIM_CHANNEL_ID, NIM_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
channel.setDescription(NIM_CHANNEL_DESC);
channel.enableVibration(true);
channel.setShowBadge(false);
return channel;
}
private static boolean isBuildAndTargetO(Context context) {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O;
}
private static void configLanguage(Context context) {
Locale locale = context.getResources().getConfiguration().locale;
String language = locale.getLanguage();
if (language != null && language.endsWith("zh")) {
// default channel
NIM_CHANNEL_NAME = "音视频聊天通知";
NIM_CHANNEL_DESC = "音视频聊天通知";
}
}
}
package com.yidianling.avchatkit.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.TelephonyManager;
/**
* 来电状态监听
* <p/>
* Created by huangjun on 2015/5/13.
*/
public class IncomingCallReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) {
final String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
PhoneCallStateObserver.getInstance().onCallStateChanged(state);
}
}
}
package com.yidianling.avchatkit.receiver;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import com.netease.nimlib.sdk.Observer;
import com.netease.nimlib.sdk.avchat.AVChatCallback;
import com.netease.nimlib.sdk.avchat.AVChatManager;
import com.yidianling.avchatkit.common.log.LogUtil;
import java.util.ArrayList;
import java.util.List;
/**
* Created by huangjun on 2015/5/13.
*/
public class PhoneCallStateObserver {
public enum PhoneCallStateEnum {
IDLE, // 空闲
INCOMING_CALL, // 有来电
DIALING_OUT, // 呼出电话已经接通
DIALING_IN // 来电已接通
}
private final String TAG = "PhoneCallStateObserver";
private int phoneState = TelephonyManager.CALL_STATE_IDLE;
private PhoneCallStateEnum stateEnum = PhoneCallStateObserver.PhoneCallStateEnum.IDLE;
private List<Observer<Integer>> autoHangUpObservers = new ArrayList<>(1); // 与本地电话互斥的挂断监听
private static class InstanceHolder {
public final static PhoneCallStateObserver instance = new PhoneCallStateObserver();
}
private PhoneCallStateObserver() {
}
public static PhoneCallStateObserver getInstance() {
return InstanceHolder.instance;
}
public void onCallStateChanged(String state) {
Log.i(TAG, "onCallStateChanged, now state =" + state);
stateEnum = PhoneCallStateEnum.IDLE;
if (TelephonyManager.EXTRA_STATE_IDLE.equals(state)) {
phoneState = TelephonyManager.CALL_STATE_IDLE;
stateEnum = PhoneCallStateEnum.IDLE;
} else if (TelephonyManager.EXTRA_STATE_RINGING.equals(state)) {
phoneState = TelephonyManager.CALL_STATE_RINGING;
stateEnum = PhoneCallStateEnum.INCOMING_CALL;
} else if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(state)) {
int lastPhoneState = phoneState;
phoneState = TelephonyManager.CALL_STATE_OFFHOOK;
if (lastPhoneState == TelephonyManager.CALL_STATE_IDLE) {
stateEnum = PhoneCallStateEnum.DIALING_OUT;
} else if (lastPhoneState == TelephonyManager.CALL_STATE_RINGING) {
stateEnum = PhoneCallStateEnum.DIALING_IN;
}
}
handleLocalCall();
}
/**
* 处理本地电话与网络通话的互斥
*/
public void handleLocalCall() {
LogUtil.i(TAG, "notify phone state changed, state=" + stateEnum.name());
if (stateEnum != PhoneCallStateEnum.IDLE) {
AVChatManager.getInstance().hangUp2(AVChatManager.getInstance().getCurrentChatId(), new HandleLocalCallCallback(1));
}
}
public PhoneCallStateEnum getPhoneCallState() {
return stateEnum;
}
private class HandleLocalCallCallback implements AVChatCallback<Void> {
private int reason;
private String log;
public HandleLocalCallCallback(int reason) {
this.reason = reason;
this.log = "handle local call";
}
@Override
public void onSuccess(Void param) {
notifyObservers(autoHangUpObservers, reason);
}
@Override
public void onFailed(int code) {
notifyObservers(autoHangUpObservers, -1 * reason);
}
@Override
public void onException(Throwable exception) {
notifyObservers(autoHangUpObservers, 0);
if (!TextUtils.isEmpty(log)) {
LogUtil.i(TAG, log + " throws exception, e=" + exception.getMessage());
}
}
}
private <T> void notifyObservers(List<Observer<T>> observers, T result) {
if (observers == null || observers.isEmpty()) {
return;
}
// 创建副本,为了使得回调到app后,app如果立即注销观察者,会造成List异常。
List<Observer<T>> copy = new ArrayList<>(observers.size());
copy.addAll(observers);
for (Observer<T> o : copy) {
o.onEvent(result);
}
}
private <T> void registerObservers(List<Observer<T>> observers, final Observer<T> observer, boolean register) {
if (observers == null || observer == null) {
return;
}
if (register) {
observers.add(observer);
} else {
observers.remove(observer);
}
}
/**
* 监听网络通话发起,接听或正在进行时有本地来电的通知
* 网络通话发起或者正在接通时,需要监听是否有本地来电(用户接通本地来电)。
* 若有本地来电,目前Demo中示例代码的处理是网络通话自动拒绝或者挂断,开发者可以自行灵活处理。
*/
public void observeAutoHangUpForLocalPhone(Observer<Integer> observer, boolean register) {
LogUtil.i(TAG, "observeAutoHangUpForLocalPhone->" + observer + "#" + register);
registerObservers(this.autoHangUpObservers, observer, register);
}
}
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