usernames = EMClient.getInstance().contactManager().getBlackListFromServer();
+
+ // in case that logout already before server returns, we should return immediately
+ if(!isLoggedIn()){
+ isBlackListSyncedWithServer = false;
+ isSyncingBlackListWithServer = false;
+ notifyBlackListSyncListener(false);
+ return;
+ }
+
+ demoModel.setBlacklistSynced(true);
+
+ isBlackListSyncedWithServer = true;
+ isSyncingBlackListWithServer = false;
+
+ notifyBlackListSyncListener(true);
+ if(callback != null){
+ callback.onSuccess(usernames);
+ }
+ } catch (HyphenateException e) {
+ demoModel.setBlacklistSynced(false);
+
+ isBlackListSyncedWithServer = false;
+ isSyncingBlackListWithServer = true;
+ e.printStackTrace();
+
+ if(callback != null){
+ callback.onError(e.getErrorCode(), e.toString());
+ }
+ }
+
+ }
+ }.start();
+ }
+
+ public void notifyBlackListSyncListener(boolean success){
+ for (DataSyncListener listener : syncBlackListListeners) {
+ listener.onSyncComplete(success);
+ }
+ }
+
+ public boolean isSyncingGroupsWithServer() {
+ return isSyncingGroupsWithServer;
+ }
+
+ public boolean isSyncingContactsWithServer() {
+ return isSyncingContactsWithServer;
+ }
+
+ public boolean isSyncingBlackListWithServer() {
+ return isSyncingBlackListWithServer;
+ }
+
+ public boolean isGroupsSyncedWithServer() {
+ return isGroupsSyncedWithServer;
+ }
+
+ public boolean isContactsSyncedWithServer() {
+ return isContactsSyncedWithServer;
+ }
+
+ public boolean isBlackListSyncedWithServer() {
+ return isBlackListSyncedWithServer;
+ }
+
+ synchronized void reset(){
+ isSyncingGroupsWithServer = false;
+ isSyncingContactsWithServer = false;
+ isSyncingBlackListWithServer = false;
+
+ demoModel.setGroupsSynced(false);
+ demoModel.setContactSynced(false);
+ demoModel.setBlacklistSynced(false);
+
+ isGroupsSyncedWithServer = false;
+ isContactsSyncedWithServer = false;
+ isBlackListSyncedWithServer = false;
+
+ isGroupAndContactListenerRegisted = false;
+
+ setContactList(null);
+// setRobotList(null);
+ getUserProfileManager().reset();
+ DemoDBManager.getInstance().closeDB();
+ }
+
+ public void pushActivity(Activity activity) {
+ easeUI.pushActivity(activity);
+ }
+
+ public void popActivity(Activity activity) {
+ easeUI.popActivity(activity);
+ }
+
+}
diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/Environment2.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/Environment2.java
new file mode 100644
index 0000000..c6ab4ff
--- /dev/null
+++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/Environment2.java
@@ -0,0 +1,39 @@
+package com.example.nanchen.aiyaschoolpush.helper;
+
+import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
+
+import com.example.nanchen.aiyaschoolpush.App;
+
+/**
+ * 表示当前应用及系统的环境信息
+ *
+ * */
+public class Environment2 {
+ /**
+ * 获取Application Context
+ * */
+ public static Context getAppContext() {
+ return App.getAppContext();
+ }
+
+ public static int getPackageVersionCode() {
+ try {
+ Context ctx = App.getAppContext();
+ return ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0).versionCode;
+ } catch (NameNotFoundException e) {
+ e.printStackTrace();
+ return 1;
+ }
+ }
+
+ public static String getPackageVersionName() {
+ try {
+ Context ctx = App.getAppContext();
+ return ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0).versionName;
+ } catch (NameNotFoundException e) {
+ e.printStackTrace();
+ return "1.0";
+ }
+ }
+}
diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/FileHelper.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/FileHelper.java
new file mode 100644
index 0000000..c6d4175
--- /dev/null
+++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/FileHelper.java
@@ -0,0 +1,364 @@
+package com.example.nanchen.aiyaschoolpush.helper;
+
+import android.content.Context;
+import android.media.ExifInterface;
+import android.os.Environment;
+import android.os.StatFs;
+
+import com.example.nanchen.aiyaschoolpush.App;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * 文件工具类
+ *
+ * @author Frank
+ * @email frank@mondol.info
+ * @create_date 2015-2
+ */
+public class FileHelper {
+
+ public static final String AUDIO = Environment.DIRECTORY_MUSIC;
+ public static final String IMAGE = Environment.DIRECTORY_PICTURES;
+ public static final String OTHER = Environment.DIRECTORY_PICTURES;
+
+ static boolean mExternalStorageAvailable = false;
+ static boolean mExternalStorageWriteable = false;
+
+ /**
+ * 检索当前系统是否包含扩展存储卡
+ *
+ * @return 0:没有存储卡|1:有只读存储卡|2:有可读写存储卡
+ */
+ public static int hasExternalStorage() {
+ final String state = Environment.getExternalStorageState();
+ if (Environment.MEDIA_MOUNTED.equals(state)) {
+ // We can read and write the media
+ return 2;
+ } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
+ return 1;
+ } else {
+ // 其它状态是错误的,即不能读也不能写
+ return 0;
+ }
+ }
+
+ /**
+ * 获取扩展存储卡剩余空间
+ */
+ public static long getAvailableExternalStorageSize() {
+ if (2 == hasExternalStorage()) {
+ StatFs localStatFs = new StatFs(Environment.getExternalStorageDirectory().getPath());
+
+ long blockSize = localStatFs.getBlockSize();
+ long blockCount = localStatFs.getAvailableBlocks();
+ return blockSize * blockCount;
+ }
+ return 0;
+ }
+
+ /**
+ * 复制一个文件
+ */
+ public static int copyFile(String fromFile, String toFile) {
+ try {
+ InputStream fosfrom = new FileInputStream(fromFile);
+ FileOutputStream fosto = new FileOutputStream(toFile);
+ byte[] bys = new byte[4096];
+ int c;
+ while ((c = fosfrom.read(bys)) > 0) {
+ fosto.write(bys, 0, c);
+ }
+ fosfrom.close();
+ fosto.close();
+ return 0;
+
+ } catch (Exception ex) {
+ return -1;
+ }
+ }
+
+ /**
+ * 获取files目录,优先返回存储卡中的目录
+ */
+ public static File getFilesDir() {
+ Context ctx = Environment2.getAppContext();
+ if (2 == hasExternalStorage()) {
+ return ctx.getExternalFilesDir(null);
+ } else {
+ return ctx.getFilesDir();
+ }
+ }
+
+ /**
+ * 获取缓存路径(优先返回存储卡中的目录)
+ */
+ public static File getCacheDir() {
+ Context ctx = Environment2.getAppContext();
+ if (2 == hasExternalStorage()) {
+ return ctx.getExternalCacheDir();
+ } else {
+ return ctx.getCacheDir();
+ }
+ }
+
+ /**
+ * 返回指定目录中一个唯一的空文件
+ *
+ * 注:方法成功返回时文件已创建,用后请删除
+ *
+ * @param fDir 文件目录 如果为null则会在扩展存储卡中的CacheDir中生成文件
+ * 如果扩展存储卡不存在则在应用CacheDir中生成文件
+ * @param suffix 文件后缀 null则使用.tmp
+ * @return 返回则返回null
+ */
+ public static File createTempFile(File fDir, String suffix) {
+ Context context = Environment2.getAppContext();
+ if (fDir == null) {
+ if (2 == hasExternalStorage()) {
+ fDir = context.getExternalCacheDir();
+ } else {
+ fDir = context.getCacheDir();
+ }
+ }
+
+ SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHH");
+ String prefix = sdf.format(new Date());
+ try {
+ return File.createTempFile(prefix, suffix, fDir);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * 获取一个空的临时文件
+ *
+ * @throws IOException
+ */
+ public static File createTempFile(String suffix) {
+ return createTempFile(null, suffix);
+ }
+
+ /**
+ * 获取随机图片文件名
+ *
+ * @return
+ * @author Williams
+ */
+ static public File getImageFilePath() {
+ String suffix = ".jpg";
+ File dir = getDirByType(IMAGE);
+ File file = getFile(suffix, dir);
+ return file;
+ }
+
+ /**
+ * 获取图片和音频外的文件名
+ *
+ * @return
+ * @author Williams
+ */
+ static public File getOtherFilePath(String sufix) {
+ String suffix = "." + sufix;
+ File dir = getDirByType(OTHER);
+ File file = getFile(suffix, dir);
+ return file;
+ }
+
+ /**
+ * 获取音频随机文件名
+ *
+ * @return
+ * @author Williams
+ */
+ static public File getAudioFilePath() {
+ String suffix = ".amr";
+ File dir = getDirByType(AUDIO);
+ File file = getFile(suffix, dir);
+ return file;
+ }
+
+ /**
+ * 获取音频随机文件名
+ *
+ * @return
+ * @author Williams
+ */
+ public static File getFile(String suffix, File dir) {
+ if (dir == null) {
+ throw new NullPointerException("Dir can not be null!");
+ } else if (!dir.exists()) {
+ dir.mkdirs();
+ }
+ try {
+ return File.createTempFile("fx_temp_", suffix, dir);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * 获取图片文件扩展信息(方向)
+ *
+ * @return int 图片的旋转角度
+ * @author Williams
+ */
+ public static int getExifOrientation(String filepath) {
+ int degree = 0;
+ ExifInterface exif = null;
+ try {
+ exif = new ExifInterface(filepath);
+ } catch (IOException ex) {
+ }
+ if (exif != null) {
+ int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
+ if (orientation != -1) {
+ // We only recognize a subset of orientation tag values.
+ switch (orientation) {
+ case ExifInterface.ORIENTATION_ROTATE_90:
+ degree = 90;
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_180:
+ degree = 180;
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_270:
+ degree = 270;
+ break;
+ }
+
+ }
+ }
+ return degree;
+ }
+
+ /**
+ * 检测外部存储卡状态
+ *
+ * @return
+ * @author Williams
+ */
+ private static void checkStorageState() {
+ final String state = Environment.getExternalStorageState();
+
+ if (Environment.MEDIA_MOUNTED.equals(state)) {
+ // 外部存储卡可读写
+ mExternalStorageAvailable = mExternalStorageWriteable = true;
+ } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
+ // 外部存储卡只读
+ mExternalStorageAvailable = true;
+ mExternalStorageWriteable = false;
+ } else {
+ // 外部存储卡不可用
+ mExternalStorageAvailable = mExternalStorageWriteable = false;
+ }
+ }
+
+ private static Context getContext() {
+ return App.getAppContext().getApplicationContext();
+ }
+
+ /**
+ * 获取音频随机文件
+ *
+ * @return
+ * @author Williams
+ */
+ public static File getAudioDir() {
+ return getDirByType(AUDIO);
+ }
+
+ /**
+ * 获取图片随机文件
+ *
+ * @return
+ * @author Williams
+ */
+ public static File getImageDir() {
+ return getDirByType(IMAGE);
+ }
+
+ /**
+ * @return
+ * @author Williams
+ */
+ static public File getDirByType(String typeName) {
+ checkStorageState();
+ final Context context = getContext();
+ if (mExternalStorageWriteable) {
+ return context.getExternalFilesDir(typeName);
+ } else {
+ return context.getCacheDir();
+ }
+ }
+
+ /**
+ * @return
+ * @author WLF
+ */
+ public static File getFileDir() {
+ checkStorageState();
+ final Context context = getContext();
+ if (mExternalStorageWriteable) {
+ return context.getExternalFilesDir(null);
+ } else {
+ return context.getCacheDir();
+ }
+ }
+
+ /**
+ * 检测SD卡是否可写
+ *
+ * @return
+ * @author WLF
+ */
+ public static boolean isSdcardWriteable() {
+ checkStorageState();
+ return mExternalStorageWriteable;
+ }
+
+ /**
+ * 检测文件是否可用
+ *
+ * @return
+ * @author WLF
+ */
+ public static boolean checkFile(File f) {
+ if (f != null && f.exists() && f.canRead() && (f.isDirectory() || (f.isFile() && f.length() > 0))) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 检测文件是否可用
+ *
+ * @return
+ * @author WLF
+ */
+ public static boolean checkFile(String path) {
+ if (StringHelper.isNotEmpty(path)) {
+ File f = new File(path);
+ if (f != null && f.exists() && f.canRead() && (f.isDirectory() || (f.isFile() && f.length() > 0)))
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean tryDeleteFile(File file) {
+ try {
+ file.delete();
+ return true;
+ } catch (Throwable t) {
+ return false;
+ }
+ }
+}
diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/FontCustomHelper.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/FontCustomHelper.java
new file mode 100644
index 0000000..3f635db
--- /dev/null
+++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/FontCustomHelper.java
@@ -0,0 +1,39 @@
+package com.example.nanchen.aiyaschoolpush.helper;
+
+import android.content.Context;
+import android.graphics.Typeface;
+
+/**
+ * 统一 字体加载类
+ *
+ * 第一次加载在MainActivity 中调用
+ * (字体加载为相对耗时操作 所以做成单例)
+ */
+public class FontCustomHelper {
+
+ private Typeface typeface;
+ private static final String FONT_URL = "fonts/icomoon.ttf";
+ private static class Holder{
+ private static FontCustomHelper helper = new FontCustomHelper();
+ }
+
+ public static FontCustomHelper getInstance(){
+ return Holder.helper;
+ }
+
+
+ public void init(Context mContext){
+ typeface = Typeface.createFromAsset(mContext.getAssets(), FONT_URL);
+ }
+
+ public Typeface getTypeface(Context mContext) {
+ if (null==typeface) {
+ init(mContext);
+ }
+ return typeface;
+ }
+
+ public void setTypeface(Typeface typeface) {
+ this.typeface = typeface;
+ }
+}
diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/LocalStorageHelper.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/LocalStorageHelper.java
new file mode 100644
index 0000000..904f0ff
--- /dev/null
+++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/LocalStorageHelper.java
@@ -0,0 +1,120 @@
+package com.example.nanchen.aiyaschoolpush.helper;
+
+import android.text.TextUtils;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * 本地存储工具类
+ *
+ * @author Frank
+ * @email frank@mondol.info
+ * @create_date 2015年4月7日
+ */
+public class LocalStorageHelper {
+ private static final String FILE_TAG_IMAGE = "image";
+ private static final String FILE_TAG_AUDIO = "audio";
+ private static final String FILE_TAG_VIDEO = "video";
+ static File mFilesDir;
+ static File mUsersDir;
+
+ /**
+ * 创建一个空的临时文件,用后请删除
+ *
+ * @param suffix
+ * 文件扩展名,传null则使用.tmp
+ * @return null则失败
+ */
+ public static File createTempFile(String suffix) {
+ return FileHelper.createTempFile(suffix);
+ }
+
+ /**
+ * 创建班级圈视频
+ *
+ * 返回文件路径,不需要请删除
+ */
+ public static File createGroupVideoFile(long groupId) {
+ return createUserTempFile("circle_" + groupId, FILE_TAG_VIDEO, ".mp4");
+ }
+
+ /**
+ * 创建一个空的讨论组图标文件
+ */
+ public static File createDiscussGroupIconFile(String jid) {
+ return createUserTempFile("group_" + jid, null, ".jpg");
+ }
+
+ /**
+ * 创建一个空的用于聊天的图片文件
+ *
+ * @param userId
+ * 用户ID
+ */
+ public static File createChatImageFile(long userId) {
+ return createUserTempFile("chat_" + userId, FILE_TAG_IMAGE, ".jpg");
+ }
+
+ /**
+ * 创建一个空的用于启动页广告图片文件
+ */
+ public static File createAdImageFile() {
+ return createUserTempFile("start_ad", FILE_TAG_IMAGE, ".jpg");
+ }
+ /**
+ * 创建一个空的用于聊天的图片文件
+ *
+ * @param jid
+ * 讨论组ID
+ * @return
+ */
+ public static File createChatImageFile(String jid) {
+ return createUserTempFile("chat_" + jid, FILE_TAG_IMAGE, ".jpg");
+ }
+
+ /**
+ * 创建一个空的用于聊天的语音文件
+ *
+ * @param userId
+ * @return
+ */
+ public static File createChatAudioFile(long userId) {
+ return createUserTempFile("chat_" + userId, FILE_TAG_AUDIO, ".amr");
+ }
+
+ /**
+ * 创建一个空的用于聊天的语音文件
+ *
+ * @param jid
+ * @return
+ */
+ public static File createChatAudioFile(String jid) {
+ return createUserTempFile("chat_" + jid, FILE_TAG_AUDIO, ".amr");
+ }
+
+ private static File createUserTempFile(String uidOrJid, String fileTag, String suffix) {
+ if (mFilesDir == null)
+ mFilesDir = FileHelper.getFilesDir();
+ if (mUsersDir == null) {
+ mUsersDir = new File(mFilesDir, "users");
+ mUsersDir.mkdir();
+ }
+
+ String userDir = TextUtils.isEmpty(uidOrJid) ? "empty" : StringHelper.getMD5String(uidOrJid);
+ File fDir = new File(mUsersDir, userDir);
+ fDir.mkdir();
+
+ if (fileTag != null) {
+ fDir = new File(fDir, fileTag);
+ fDir.mkdir();
+ }
+
+ try {
+ return File.createTempFile(fileTag != null ? fileTag : "file", suffix, fDir);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+}
diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/QiYuCloudServerHelper.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/QiYuCloudServerHelper.java
new file mode 100644
index 0000000..52cb950
--- /dev/null
+++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/QiYuCloudServerHelper.java
@@ -0,0 +1,225 @@
+package com.example.nanchen.aiyaschoolpush.helper;
+
+import android.app.Activity;
+import android.app.Application;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+
+import com.alibaba.fastjson.JSONArray;
+import com.example.nanchen.aiyaschoolpush.R;
+import com.qiyukf.nimlib.sdk.NIMClient;
+import com.qiyukf.nimlib.sdk.msg.MsgService;
+import com.qiyukf.nimlib.sdk.msg.constant.SessionTypeEnum;
+import com.qiyukf.unicorn.api.ImageLoaderListener;
+import com.qiyukf.unicorn.api.StatusBarNotificationConfig;
+import com.qiyukf.unicorn.api.UICustomization;
+import com.qiyukf.unicorn.api.Unicorn;
+import com.qiyukf.unicorn.api.UnicornImageLoader;
+import com.qiyukf.unicorn.api.YSFOptions;
+import com.qiyukf.unicorn.api.YSFUserInfo;
+import com.squareup.picasso.Picasso;
+import com.squareup.picasso.RequestCreator;
+import com.squareup.picasso.Target;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.HashSet;
+import java.util.Set;
+
+
+/**
+ * @author nanchen
+ * @fileName AiYaSchoolPush
+ * @packageName com.example.nanchen.aiyaschoolpush.helper
+ * @date 2016/11/04 15:47
+ *
+ * 七鱼云客服相关帮助类
+ */
+
+public class QiYuCloudServerHelper {
+
+
+ private static Context mContext;
+ private static YSFOptions mOptins;
+
+ private final static String APP_KEY = "2c170d44ffed6896868d56a13a09f63c";
+
+
+ /**
+ * 初始化七鱼云客服
+ */
+ public static void initCloudServer(Application app) {
+ mContext = app;
+ Unicorn.init(app, APP_KEY, getServerOptions(), new PicassoImageLoader());
+ }
+
+ private static YSFOptions getServerOptions() {
+ if (mOptins == null) {
+ YSFOptions options = new YSFOptions();
+ // 通知栏提醒
+ options.statusBarNotificationConfig = new StatusBarNotificationConfig();
+ options.statusBarNotificationConfig.notificationSmallIconId = R.mipmap.icon_notify;
+ // UI定制 头像版本更新,不再支持drawable://格式
+ UICustomization customization = new UICustomization();
+// customization.msgBackgroundColor = mContext.getResources().getColor(R.color.main_bg_color);
+// customization.tipsTextColor = mContext.getResources().getColor(R.color.gray3);
+// customization.msgListViewDividerHeight = 40;
+// customization.leftAvatar = "file://" + R.drawable.service;
+// customization.tipsTextSize = 12;
+// customization.msgItemBackgroundLeft = R.drawable.qiyu_message_item_selector;
+// customization.msgItemBackgroundRight = R.drawable.qiyu_message_item_selector;
+// customization.textMsgColorLeft = mContext.getResources().getColor(R.color.srs_text);
+// customization.textMsgColorRight = mContext.getResources().getColor(R.color.srs_text);
+// customization.titleBackgroundColor = mContext.getResources().getColor(R.color.main_bg_color1);
+// customization.textMsgSize = 18;
+// options.uiCustomization = customization;
+// options.savePowerConfig = new SavePowerConfig();//省电策略
+ mOptins = options;
+ }
+ return mOptins;
+ }
+
+
+ /**
+ * 设置客服消息提醒的跳转入口
+ */
+ private static void setNotifyActivity(Activity activity) {
+ mOptins.statusBarNotificationConfig.notificationEntrance = activity.getClass();
+ }
+
+ /**
+ * 启用通知
+ *
+ * @param activity 通知跳转入口
+ */
+ public static void enableNotify(Activity activity) {
+ setNotifyActivity(activity);
+ NIMClient.getService(MsgService.class).setChattingAccount(MsgService.MSG_CHATTING_ACCOUNT_NONE, SessionTypeEnum.None);
+ }
+
+ /**
+ * 禁用通知
+ */
+ public static void disableNotify() {
+ NIMClient.getService(MsgService.class).setChattingAccount(MsgService.MSG_CHATTING_ACCOUNT_ALL, SessionTypeEnum.None);
+ }
+
+ /**
+ * 设置用户信息
+ *
+ * @param login 当前用户是否处于登录状态
+ */
+ public static void setUserInfo(boolean login) {
+ Unicorn.setUserInfo(getCurrentUserInfo(login));
+ }
+
+
+ /**
+ * 设置当前登录用户信息
+ *
+ * @param login 当前用户是否处于登录状态
+ * @return
+ */
+ private static YSFUserInfo getCurrentUserInfo(boolean login) {
+ YSFUserInfo userInfo = new YSFUserInfo();
+ userInfo.data = userInfoData(login).toString();
+ if (login) {
+ userInfo.userId = DemoHelper.getInstance().getCurrentUserName();
+ }
+ return userInfo;
+ }
+
+
+ private static JSONArray userInfoData(boolean login) {
+ String userName = DemoHelper.getInstance().getCurrentUserName();
+ String platform = null;
+ JSONArray array = new JSONArray();
+ if (login){
+ array.add(userInfoDataItem("real_name", userName, false, -1, null, null));
+ }
+ array.add(userInfoDataItem("app_version", platform + "-" + Environment2.getPackageVersionName(), false, 0, "应用版本", null));
+ array.add(userInfoDataItem("system_version", android.os.Build.VERSION.RELEASE, false, 1, "系统版本", null));
+ array.add(userInfoDataItem("device_manufacturer", Build.MANUFACTURER, false, 2, "机型", null));
+ array.add(userInfoDataItem("device_model", Build.MODEL, false, 3, "模块", null));
+ return array;
+ }
+
+ private static JSONObject userInfoDataItem(String key, Object value, boolean hidden, int index, String label, String href) {
+ JSONObject item = null;
+ try {
+ item = new JSONObject();
+ item.put("key", key);
+ item.put("value", value);
+ if (hidden) {
+ item.put("hidden", true);
+ }
+ if (index >= 0) {
+ item.put("index", index);
+ }
+ if (!TextUtils.isEmpty(label)) {
+ item.put("label", label);
+ }
+ if (!TextUtils.isEmpty(href)) {
+ item.put("href", href);
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return item;
+ }
+
+ private static class PicassoImageLoader implements UnicornImageLoader {
+
+ private final Set protectedFromGarbageCollectorTargets = new HashSet<>();
+
+ @Nullable
+ @Override
+ public Bitmap loadImageSync(String uri, int width, int height) {
+ return null;
+ }
+
+ @Override
+ public void loadImage(final String uri, final int width, final int height, final ImageLoaderListener listener) {
+ final Target mTarget = new Target() {
+ @Override
+ public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
+ if (listener != null) {
+ listener.onLoadComplete(bitmap);
+ protectedFromGarbageCollectorTargets.remove(this);
+ }
+ }
+
+ @Override
+ public void onBitmapFailed(Drawable errorDrawable) {
+ if (listener != null) {
+ listener.onLoadFailed(null);
+ protectedFromGarbageCollectorTargets.remove(this);
+ }
+ }
+
+ @Override
+ public void onPrepareLoad(Drawable placeHolderDrawable) {
+
+ }
+ };
+ ((Activity)mContext).runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ RequestCreator requestCreator = Picasso.with(mContext).load(uri).config(Bitmap.Config.RGB_565);
+ if (width > 0 && height > 0) {
+ requestCreator = requestCreator.resize(width, height);
+ }
+ protectedFromGarbageCollectorTargets.add(mTarget);
+ requestCreator.into(mTarget);
+ }
+ });
+ }
+ }
+
+
+}
diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/StringHelper.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/StringHelper.java
new file mode 100644
index 0000000..c8cbfc9
--- /dev/null
+++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/StringHelper.java
@@ -0,0 +1,194 @@
+package com.example.nanchen.aiyaschoolpush.helper;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.regex.Pattern;
+import java.util.zip.CRC32;
+
+/**
+ * 字符串相关的Helper方法
+ *
+ * */
+public final class StringHelper {
+ public static final String EMPTY = "";
+ private final static Pattern pattern_isEmail = Pattern.compile("^[a-z0-9]+[\\w\\-\\.]*@[a-z0-9\\-]+(?:\\.[a-z0-9\\-]+)+$", Pattern.CASE_INSENSITIVE);
+ private final static Pattern pattern_isMobile = Pattern.compile("^1[3-9][0-9]{9}$");
+ private final static Pattern pattern_isPassword = Pattern.compile(".{6,30}");
+
+ public static boolean isNullOrEmpty(String str) {
+ return str == null || str.length() == 0;
+ }
+
+ public static boolean isNullOrWhiteSpace(String str) {
+ return str == null || str.length() == 0 || str.trim().length() == 0;
+ }
+
+ public static boolean isEmail(String str) {
+ return pattern_isEmail.matcher(str).matches();
+ }
+
+ /**
+ * 判断指定的字符串是否是手机号
+ * */
+ public static boolean isMobile(String str) {
+ return pattern_isMobile.matcher(str).matches();
+ }
+
+ /**
+ * 获取指定字符串中 {@code lastChar} 前面的部分 如果不包含{@code lastChar}则返回原{@code str}
+ * */
+ public static String getLeftStringByLastChar(String str, char lastChar) {
+ if (str == null)
+ return null;
+ int index = str.lastIndexOf(lastChar);
+ if (index == -1)
+ return str;
+
+ return str.substring(0, index);
+ }
+
+ /**
+ * 获取指定字符串中 {@code lastChar} 后面的部分 如果不包含{@code lastChar}则返回原{@code str}
+ * */
+ public static String getRightStringByLastChar(String str, char lastChar) {
+ if (str == null)
+ return null;
+ int index = str.lastIndexOf(lastChar);
+ if (index == -1)
+ return str;
+
+ return str.substring(index + 1);
+ }
+
+ public static String bytesToHexString(byte[] bytes) {
+ StringBuilder sbHex = new StringBuilder(bytes.length * 2);
+ for (byte b : bytes) {
+ if ((b & 0xFF) < 0x10)
+ sbHex.append("0");
+ sbHex.append(Integer.toHexString(b & 0xFF));
+ }
+ return sbHex.toString();
+ }
+
+ public static String getMD5String(String str) {
+ try {
+ return getMD5String(str, "utf-8");
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public static String getMD5String(String str, String charsetName) throws UnsupportedEncodingException {
+ try {
+ return getHashString("MD5", str, charsetName);
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ throw new RuntimeException("当前环境不支持MD5算法!");
+ }
+ }
+
+ public static String getSHA1String(String str) {
+ try {
+ return getSHA1String(str, "utf-8");
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public static String getSHA1String(String str, String charsetName) throws UnsupportedEncodingException {
+ try {
+ return getHashString("SHA-1", str, charsetName);
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ throw new RuntimeException("当前环境不支持SHA-1算法!");
+ }
+ }
+
+ public static String getCRC32String(String str) {
+ try {
+ return getCRC32String(str, "utf-8");
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public static String getCRC32String(String str, String charsetName) throws UnsupportedEncodingException {
+ try {
+ return getHashString("CRC32", str, charsetName);
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ throw new RuntimeException("当前环境不支持CRC32算法!");
+ }
+ }
+
+ private static String getHashString(String algorithm, String str, String charsetName) throws UnsupportedEncodingException, NoSuchAlgorithmException {
+ if ("CRC32".equals(algorithm)) {
+ CRC32 crc = new CRC32();
+ crc.update(str.getBytes(charsetName));
+ long lVal = crc.getValue();
+ return Long.toHexString(lVal).toLowerCase();
+ } else {
+ byte[] bysHash = MessageDigest.getInstance(algorithm).digest(str.getBytes(charsetName));
+ return bytesToHexString(bysHash);
+ }
+ }
+
+ public static boolean isValidUserName(String str) {
+ final int len = str.length();
+ boolean allNumber = true;
+ for (int i = 0; i < len; i++) {
+ char c = str.charAt(i);
+ if ('0' > c || c > '9') {
+ allNumber = false;
+ break;
+ }
+ }
+ return allNumber;
+ }
+
+ public static boolean isValidPwd(String str) {
+ return pattern_isPassword.matcher(str).matches();
+ }
+
+ /**
+ * 判断字符串是否为空
+ *
+ *
+ * @param str
+ * @return
+ *
+ * @author WLF
+ */
+ public static boolean isEmpty(String str) {
+ return str == null || str.length() == 0 || str.equalsIgnoreCase("null");
+ }
+
+ /**
+ * 判断字符串不为空
+ *
+ *
+ * @param str
+ * @return
+ *
+ * @author WLF
+ */
+ public static boolean isNotEmpty(String str) {
+ return !isEmpty(str);
+ }
+
+ /**
+ *
+ *
+ * @param str
+ * @return
+ *
+ * @author WLF
+ */
+ public static String trim(String str) {
+ return str == null ? EMPTY : str.trim();
+ }
+}
diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/CommunityEvent.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/CommunityEvent.java
new file mode 100644
index 0000000..c991cc1
--- /dev/null
+++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/CommunityEvent.java
@@ -0,0 +1,27 @@
+package com.example.nanchen.aiyaschoolpush.helper.event;
+
+import com.example.nanchen.aiyaschoolpush.model.info.InfoModel;
+
+/**
+ * @author nanchen
+ * @fileName AiYaSchoolPush
+ * @packageName com.example.nanchen.aiyaschoolpush
+ * @date 2016/11/23 13:46
+ */
+
+public class CommunityEvent {
+ private InfoModel mInfoModel;
+
+ public CommunityEvent(InfoModel infoModel) {
+ mInfoModel = infoModel;
+ }
+ public CommunityEvent(){}
+
+ public InfoModel getInfoModel() {
+ return mInfoModel;
+ }
+
+ public void setInfoModel(InfoModel infoModel) {
+ mInfoModel = infoModel;
+ }
+}
diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/HomeworkEvent.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/HomeworkEvent.java
new file mode 100644
index 0000000..5f2dcc5
--- /dev/null
+++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/HomeworkEvent.java
@@ -0,0 +1,28 @@
+package com.example.nanchen.aiyaschoolpush.helper.event;
+
+import com.example.nanchen.aiyaschoolpush.model.info.InfoModel;
+
+/**
+ * @author nanchen
+ * @fileName AiYaSchoolPush
+ * @packageName com.example.nanchen.aiyaschoolpush
+ * @date 2016/11/22 13:47
+ */
+
+public class HomeworkEvent {
+ private InfoModel mInfoModel;
+
+ public HomeworkEvent(InfoModel infoModel){
+ this.mInfoModel = infoModel;
+ }
+
+ public HomeworkEvent(){}
+
+ public InfoModel getInfoModel() {
+ return mInfoModel;
+ }
+
+ public void setInfoModel(InfoModel infoModel) {
+ mInfoModel = infoModel;
+ }
+}
diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/NetStateEvent.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/NetStateEvent.java
new file mode 100644
index 0000000..4101959
--- /dev/null
+++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/NetStateEvent.java
@@ -0,0 +1,13 @@
+package com.example.nanchen.aiyaschoolpush.helper.event;
+
+/**
+ * @author nanchen
+ * @fileName AiYaSchoolPush
+ * @packageName com.example.nanchen.aiyaschoolpush.helper.event
+ * @date 2016/12/01 15:19
+ */
+
+public class NetStateEvent {
+
+ public NetStateEvent(){}
+}
diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/NoticeEvent.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/NoticeEvent.java
new file mode 100644
index 0000000..980f83c
--- /dev/null
+++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/NoticeEvent.java
@@ -0,0 +1,41 @@
+package com.example.nanchen.aiyaschoolpush.helper.event;
+
+import com.example.nanchen.aiyaschoolpush.model.info.InfoModel;
+
+/**
+ * @author nanchen
+ * @fileName AiYaSchoolPush
+ * @packageName com.example.nanchen.aiyaschoolpush
+ * @date 2016/11/22 09:58
+ */
+
+public class NoticeEvent {
+ private InfoModel mInfoModel;
+ private int mCommentCount;
+ public NoticeEvent(InfoModel infoModel,int commentCount){
+ this(infoModel);
+ this.mCommentCount = commentCount;
+ }
+
+ public NoticeEvent(InfoModel infoModel){
+ this.mInfoModel = infoModel;
+ }
+
+ public NoticeEvent(){}
+
+ public int getCommentCount() {
+ return mCommentCount;
+ }
+
+ public void setCommentCount(int commentCount) {
+ mCommentCount = commentCount;
+ }
+
+ public InfoModel getInfoModel() {
+ return mInfoModel;
+ }
+
+ public void setInfoModel(InfoModel infoModel) {
+ mInfoModel = infoModel;
+ }
+}
diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/UpdateUserEvent.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/UpdateUserEvent.java
new file mode 100644
index 0000000..a622576
--- /dev/null
+++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/event/UpdateUserEvent.java
@@ -0,0 +1,14 @@
+package com.example.nanchen.aiyaschoolpush.helper.event;
+
+/**
+ * @author nanchen
+ * @fileName AiYaSchoolPush
+ * @packageName com.example.nanchen.aiyaschoolpush.event
+ * @date 2016/11/29 11:58
+ */
+
+public class UpdateUserEvent {
+ public UpdateUserEvent(){
+
+ }
+}
diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/receiver/AvatarReceiver.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/receiver/AvatarReceiver.java
new file mode 100644
index 0000000..4545ec8
--- /dev/null
+++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/receiver/AvatarReceiver.java
@@ -0,0 +1,51 @@
+package com.example.nanchen.aiyaschoolpush.helper.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+/**
+ * @author nanchen
+ * @fileName AiYaSchoolPush
+ * @packageName com.example.nanchen.aiyaschoolpush.receiver
+ * @date 2016/11/11 11:40
+ *
+ * 头像改变的广播接收器,防止用户在个人信息更改图片后主页面头像未更新的问题
+ */
+
+public class AvatarReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "AvatarReceiver";
+
+ public static final String AVATAR_ACTION = "com.nanchen.android.AVATAR_ACTION";
+
+
+ private AvatarCallback mAvatarCallback;
+
+ public AvatarReceiver(AvatarCallback avatarCallback){
+ mAvatarCallback = avatarCallback;
+ }
+
+ public AvatarReceiver(){}
+
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ Log.e(TAG,action);
+ // 如果是正确的action
+ if (AVATAR_ACTION.equals(action)){
+ if (mAvatarCallback != null){
+ mAvatarCallback.onAvatarChanged();
+ }
+ }
+ }
+
+ public interface AvatarCallback{
+ /**
+ * 头像更改时调用
+ */
+ void onAvatarChanged();
+ }
+}
diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/receiver/MiMessageReceiver.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/receiver/MiMessageReceiver.java
new file mode 100644
index 0000000..cef3b6c
--- /dev/null
+++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/helper/receiver/MiMessageReceiver.java
@@ -0,0 +1,113 @@
+package com.example.nanchen.aiyaschoolpush.helper.receiver;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import com.xiaomi.mipush.sdk.ErrorCode;
+import com.xiaomi.mipush.sdk.MiPushClient;
+import com.xiaomi.mipush.sdk.MiPushCommandMessage;
+import com.xiaomi.mipush.sdk.MiPushMessage;
+import com.xiaomi.mipush.sdk.PushMessageReceiver;
+
+import java.util.List;
+
+/**
+ * 小米推送消息接收器
+ *
+ * @author nanchen
+ * @fileName AiYaSchoolPush
+ * @packageName com.example.nanchen.aiyaschoolpush.receiver
+ * @date 2016/10/31 11:59
+ */
+
+public class MiMessageReceiver extends PushMessageReceiver {
+ private String mRegId;
+ private long mResultCode = -1;
+ private String mReason;
+ private String mCommand;
+ private String mMessage;
+ private String mTopic;
+ private String mAlias;
+ private String mUserAccount;
+ private String mStartTime;
+ private String mEndTime;
+ @Override
+ public void onReceivePassThroughMessage(Context context, MiPushMessage message) {
+
+ mMessage = message.getContent();
+ if(!TextUtils.isEmpty(message.getTopic())) {
+ mTopic=message.getTopic();
+ } else if(!TextUtils.isEmpty(message.getAlias())) {
+ mAlias=message.getAlias();
+ } else if(!TextUtils.isEmpty(message.getUserAccount())) {
+ mUserAccount=message.getUserAccount();
+ }
+ }
+ @Override
+ public void onNotificationMessageClicked(Context context, MiPushMessage message) {
+ mMessage = message.getContent();
+ if(!TextUtils.isEmpty(message.getTopic())) {
+ mTopic=message.getTopic();
+ } else if(!TextUtils.isEmpty(message.getAlias())) {
+ mAlias=message.getAlias();
+ } else if(!TextUtils.isEmpty(message.getUserAccount())) {
+ mUserAccount=message.getUserAccount();
+ }
+ }
+ @Override
+ public void onNotificationMessageArrived(Context context, MiPushMessage message) {
+ mMessage = message.getContent();
+ if(!TextUtils.isEmpty(message.getTopic())) {
+ mTopic=message.getTopic();
+ } else if(!TextUtils.isEmpty(message.getAlias())) {
+ mAlias=message.getAlias();
+ } else if(!TextUtils.isEmpty(message.getUserAccount())) {
+ mUserAccount=message.getUserAccount();
+ }
+ }
+ @Override
+ public void onCommandResult(Context context, MiPushCommandMessage message) {
+ String command = message.getCommand();
+ List arguments = message.getCommandArguments();
+ String cmdArg1 = ((arguments != null && arguments.size() > 0) ? arguments.get(0) : null);
+ String cmdArg2 = ((arguments != null && arguments.size() > 1) ? arguments.get(1) : null);
+ if (MiPushClient.COMMAND_REGISTER.equals(command)) {
+ if (message.getResultCode() == ErrorCode.SUCCESS) {
+ mRegId = cmdArg1;
+ }
+ } else if (MiPushClient.COMMAND_SET_ALIAS.equals(command)) {
+ if (message.getResultCode() == ErrorCode.SUCCESS) {
+ mAlias = cmdArg1;
+ }
+ } else if (MiPushClient.COMMAND_UNSET_ALIAS.equals(command)) {
+ if (message.getResultCode() == ErrorCode.SUCCESS) {
+ mAlias = cmdArg1;
+ }
+ } else if (MiPushClient.COMMAND_SUBSCRIBE_TOPIC.equals(command)) {
+ if (message.getResultCode() == ErrorCode.SUCCESS) {
+ mTopic = cmdArg1;
+ }
+ } else if (MiPushClient.COMMAND_UNSUBSCRIBE_TOPIC.equals(command)) {
+ if (message.getResultCode() == ErrorCode.SUCCESS) {
+ mTopic = cmdArg1;
+ }
+ } else if (MiPushClient.COMMAND_SET_ACCEPT_TIME.equals(command)) {
+ if (message.getResultCode() == ErrorCode.SUCCESS) {
+ mStartTime = cmdArg1;
+ mEndTime = cmdArg2;
+ }
+ }
+ }
+ @Override
+ public void onReceiveRegisterResult(Context context, MiPushCommandMessage message) {
+ String command = message.getCommand();
+ List arguments = message.getCommandArguments();
+ String cmdArg1 = ((arguments != null && arguments.size() > 0) ? arguments.get(0) : null);
+ String cmdArg2 = ((arguments != null && arguments.size() > 1) ? arguments.get(1) : null);
+ if (MiPushClient.COMMAND_REGISTER.equals(command)) {
+ if (message.getResultCode() == ErrorCode.SUCCESS) {
+ mRegId = cmdArg1;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/Constant.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/Constant.java
new file mode 100644
index 0000000..dd975d4
--- /dev/null
+++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/Constant.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright (C) 2016 Hyphenate Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.nanchen.aiyaschoolpush.im;
+
+import com.hyphenate.easeui.EaseConstant;
+
+public class Constant extends EaseConstant{
+ public static final String NEW_FRIENDS_USERNAME = "item_new_friends";
+ public static final String GROUP_USERNAME = "item_groups";
+ public static final String CHAT_ROOM = "item_chatroom";
+ public static final String ACCOUNT_REMOVED = "account_removed";
+ public static final String ACCOUNT_CONFLICT = "conflict";
+ public static final String ACCOUNT_FORBIDDEN = "user_forbidden";
+ public static final String CHAT_ROBOT = "item_robots";
+ public static final String MESSAGE_ATTR_ROBOT_MSGTYPE = "msgtype";
+ public static final String ACTION_GROUP_CHANAGED = "action_group_changed";
+ public static final String ACTION_CONTACT_CHANAGED = "action_contact_changed";
+}
diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/DataSyncListener.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/DataSyncListener.java
new file mode 100644
index 0000000..51e4b05
--- /dev/null
+++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/DataSyncListener.java
@@ -0,0 +1,16 @@
+package com.example.nanchen.aiyaschoolpush.im;
+
+/**
+ * @author nanchen
+ * @fileName AiYaSchoolPush
+ * @packageName com.example.nanchen.aiyaschoolpush.im
+ * @date 2016/10/28 08:52
+ */
+
+public interface DataSyncListener {
+ /**
+ * sync complete
+ * @param success true:data sync successful,false: failed to sync data
+ */
+ void onSyncComplete(boolean success);
+}
diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/EmojiconExampleGroupData.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/EmojiconExampleGroupData.java
new file mode 100644
index 0000000..fe2f121
--- /dev/null
+++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/EmojiconExampleGroupData.java
@@ -0,0 +1,74 @@
+package com.example.nanchen.aiyaschoolpush.im;
+
+import com.example.nanchen.aiyaschoolpush.App;
+import com.example.nanchen.aiyaschoolpush.R;
+import com.hyphenate.easeui.domain.EaseEmojicon;
+import com.hyphenate.easeui.domain.EaseEmojicon.Type;
+import com.hyphenate.easeui.domain.EaseEmojiconGroupEntity;
+
+import java.util.Arrays;
+
+public class EmojiconExampleGroupData {
+
+ private static int[] icons = new int[]{
+ R.drawable.icon_002_cover,
+ R.drawable.icon_007_cover,
+ R.drawable.icon_010_cover,
+ R.drawable.icon_012_cover,
+ R.drawable.icon_013_cover,
+ R.drawable.icon_018_cover,
+ R.drawable.icon_019_cover,
+ R.drawable.icon_020_cover,
+ R.drawable.icon_021_cover,
+ R.drawable.icon_022_cover,
+ R.drawable.icon_024_cover,
+ R.drawable.icon_027_cover,
+ R.drawable.icon_029_cover,
+ R.drawable.icon_030_cover,
+ R.drawable.icon_035_cover,
+ R.drawable.icon_040_cover,
+ };
+
+ private static int[] bigIcons = new int[]{
+ R.drawable.icon_002,
+ R.drawable.icon_007,
+ R.drawable.icon_010,
+ R.drawable.icon_012,
+ R.drawable.icon_013,
+ R.drawable.icon_018,
+ R.drawable.icon_019,
+ R.drawable.icon_020,
+ R.drawable.icon_021,
+ R.drawable.icon_022,
+ R.drawable.icon_024,
+ R.drawable.icon_027,
+ R.drawable.icon_029,
+ R.drawable.icon_030,
+ R.drawable.icon_035,
+ R.drawable.icon_040,
+ };
+
+
+ private static final EaseEmojiconGroupEntity DATA = createData();
+
+ private static EaseEmojiconGroupEntity createData(){
+ EaseEmojiconGroupEntity emojiconGroupEntity = new EaseEmojiconGroupEntity();
+ EaseEmojicon[] datas = new EaseEmojicon[icons.length];
+ for(int i = 0; i < icons.length; i++){
+ datas[i] = new EaseEmojicon(icons[i], null, Type.BIG_EXPRESSION);
+ datas[i].setBigIcon(bigIcons[i]);
+ //you can replace this to any you want
+ datas[i].setName(App.getInstance().getApplicationContext().getString(R.string.emojicon_test_name)+ (i+1));
+ datas[i].setIdentityCode("em"+ (1000+i+1));
+ }
+ emojiconGroupEntity.setEmojiconList(Arrays.asList(datas));
+ emojiconGroupEntity.setIcon(R.drawable.ee_2);
+ emojiconGroupEntity.setType(Type.BIG_EXPRESSION);
+ return emojiconGroupEntity;
+ }
+
+
+ public static EaseEmojiconGroupEntity getData(){
+ return DATA;
+ }
+}
diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/ImageCache.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/ImageCache.java
new file mode 100644
index 0000000..d32989f
--- /dev/null
+++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/ImageCache.java
@@ -0,0 +1,563 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.nanchen.aiyaschoolpush.im;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.os.Environment;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.util.LruCache;
+
+import java.io.File;
+import java.lang.ref.SoftReference;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * This class memory caching of bitmaps in conjunction with the
+ * {@link ImageWorker} class and its subclasses. Use
+ * {@link ImageCache#getInstance(FragmentManager, ImageCacheParams)}
+ * to get an instance of this class, although usually a cache should be added
+ * directly to an {@link ImageWorker} by calling
+ * {@link ImageWorker#addImageCache(FragmentManager, ImageCacheParams)}
+ * .
+ */
+public class ImageCache {
+ private static final String TAG = "ImageCache";
+
+ // Default memory cache size in kilobytes
+ private static final int DEFAULT_MEM_CACHE_SIZE = 1024 * 5; // 5MB
+
+ private static final int DEFAULT_COMPRESS_QUALITY = 70;
+
+ // Constants to easily toggle various caches
+ private static final boolean DEFAULT_MEM_CACHE_ENABLED = true;
+ private static final boolean DEFAULT_INIT_DISK_CACHE_ON_CREATE = false;
+
+ private LruCache mMemoryCache;
+
+ private Set> mReusableBitmaps;
+
+ /**
+ * Create a new ImageCache object using the specified parameters. This
+ * should not be called directly by other classes, instead use
+ * {@link ImageCache#getInstance(FragmentManager, ImageCacheParams)}
+ * to fetch an ImageCache instance.
+ *
+ * @param cacheParams
+ * The cache parameters to use to initialize the cache
+ */
+ private ImageCache(ImageCacheParams cacheParams) {
+ init(cacheParams);
+ }
+
+ /**
+ * Return an {@link ImageCache} instance. A {@link RetainFragment} is used
+ * to retain the ImageCache object across configuration changes such as a
+ * change in device orientation.
+ *
+ * @param fragmentManager
+ * The fragment manager to use when dealing with the retained
+ * fragment.
+ * @param cacheParams
+ * The cache parameters to use if the ImageCache needs
+ * instantiation.
+ * @return An existing retained ImageCache object or a new one if one did
+ * not exist
+ */
+ public static ImageCache getInstance(FragmentManager fragmentManager,
+ ImageCacheParams cacheParams) {
+
+ // Search for, or create an instance of the non-UI RetainFragment
+ final RetainFragment mRetainFragment = findOrCreateRetainFragment(fragmentManager);
+
+ // See if we already have an ImageCache stored in RetainFragment
+ ImageCache imageCache = (ImageCache) mRetainFragment.getObject();
+
+ // No existing ImageCache, create one and store it in RetainFragment
+ if (imageCache == null) {
+ imageCache = new ImageCache(cacheParams);
+ mRetainFragment.setObject(imageCache);
+ }
+
+ return imageCache;
+ }
+
+ /**
+ * Initialize the cache, providing all parameters.
+ *
+ * @param cacheParams
+ * The cache parameters to initialize the cache
+ */
+ private void init(ImageCacheParams cacheParams) {
+ ImageCacheParams mCacheParams = cacheParams;
+
+ // BEGIN_INCLUDE(init_memory_cache)
+ // Set up memory cache
+ if (mCacheParams.memoryCacheEnabled) {
+
+ // If we're running on Honeycomb or newer, create a set of reusable
+ // bitmaps that can be
+ // populated into the inBitmap field of BitmapFactory.Options. Note
+ // that the set is
+ // of SoftReferences which will actually not be very effective due
+ // to the garbage
+ // collector being aggressive clearing Soft/WeakReferences. A better
+ // approach
+ // would be to use a strongly references bitmaps, however this would
+ // require some
+ // balancing of memory usage between this set and the bitmap
+ // LruCache. It would also
+ // require knowledge of the expected size of the bitmaps. From
+ // Honeycomb to JellyBean
+ // the size would need to be precise, from KitKat onward the size
+ // would just need to
+ // be the upper bound (due to changes in how inBitmap can re-use
+ // bitmaps).
+ if (Utils.hasHoneycomb()) {
+ mReusableBitmaps = Collections
+ .synchronizedSet(new HashSet>());
+ }
+
+ mMemoryCache = new LruCache(
+ mCacheParams.memCacheSize) {
+
+ /**
+ * Notify the removed entry that is no longer being cached
+ */
+ @Override
+ protected void entryRemoved(boolean evicted, String key,
+ BitmapDrawable oldValue, BitmapDrawable newValue) {
+ if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
+ // The removed entry is a recycling drawable, so notify
+ // it
+ // that it has been removed from the memory cache
+ ((RecyclingBitmapDrawable) oldValue).setIsCached(false);
+ } else {
+ // The removed entry is a standard BitmapDrawable
+
+ if (Utils.hasHoneycomb()) {
+ // We're running on Honeycomb or later, so add the
+ // bitmap
+ // to a SoftReference set for possible use with
+ // inBitmap later
+ mReusableBitmaps.add(new SoftReference(
+ oldValue.getBitmap()));
+ }
+ }
+ }
+
+ /**
+ * Measure item size in kilobytes rather than units which is
+ * more practical for a bitmap cache
+ */
+ @Override
+ protected int sizeOf(String key, BitmapDrawable value) {
+ final int bitmapSize = getBitmapSize(value) / 1024;
+ return bitmapSize == 0 ? 1 : bitmapSize;
+ }
+ };
+ }
+
+ }
+
+ /**
+ * Adds a bitmap to both memory and disk cache.
+ *
+ * @param data
+ * Unique identifier for the bitmap to store
+ * @param value
+ * The bitmap drawable to store
+ */
+ public void addBitmapToCache(String data, BitmapDrawable value) {
+ // BEGIN_INCLUDE(add_bitmap_to_cache)
+ if (data == null || value == null) {
+ return;
+ }
+
+ // Add to memory cache
+ if (mMemoryCache != null) {
+ if (RecyclingBitmapDrawable.class.isInstance(value)) {
+ // The removed entry is a recycling drawable, so notify it
+ // that it has been added into the memory cache
+ ((RecyclingBitmapDrawable) value).setIsCached(true);
+ }
+ mMemoryCache.put(data, value);
+ }
+
+ }
+
+ /**
+ * Get from memory cache.
+ *
+ * @param data
+ * Unique identifier for which item to get
+ * @return The bitmap drawable if found in cache, null otherwise
+ */
+ public BitmapDrawable getBitmapFromMemCache(String data) {
+ // BEGIN_INCLUDE(get_bitmap_from_mem_cache)
+ BitmapDrawable memValue = null;
+
+ if (mMemoryCache != null) {
+ memValue = mMemoryCache.get(data);
+ }
+
+// if (BuildConfig.DEBUG && memValue != null) {
+// Log.d(TAG, "Memory cache hit");
+// }
+
+ return memValue;
+ // END_INCLUDE(get_bitmap_from_mem_cache)
+ }
+
+ /**
+ * @param options
+ * - BitmapFactory.Options with out* options populated
+ * @return Bitmap that case be used for inBitmap
+ */
+ protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
+ // BEGIN_INCLUDE(get_bitmap_from_reusable_set)
+ Bitmap bitmap = null;
+
+ if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
+ synchronized (mReusableBitmaps) {
+ final Iterator> iterator = mReusableBitmaps
+ .iterator();
+ Bitmap item;
+
+ while (iterator.hasNext()) {
+ item = iterator.next().get();
+
+ if (null != item && item.isMutable()) {
+ // Check to see it the item can be used for inBitmap
+ if (canUseForInBitmap(item, options)) {
+ bitmap = item;
+
+ // Remove from reusable set so it can't be used
+ // again
+ iterator.remove();
+ break;
+ }
+ } else {
+ // Remove from the set if the reference has been
+ // cleared.
+ iterator.remove();
+ }
+ }
+ }
+ }
+
+ return bitmap;
+ // END_INCLUDE(get_bitmap_from_reusable_set)
+ }
+
+ /**
+ * Clears both the memory and disk cache associated with this ImageCache
+ * object. Note that this includes disk access so this should not be
+ * executed on the main/UI thread.
+ */
+ public void clearCache() {
+ if (mMemoryCache != null) {
+ mMemoryCache.evictAll();
+// if (BuildConfig.DEBUG) {
+// Log.d(TAG, "Memory cache cleared");
+// }
+ }
+
+ }
+
+ /**
+ * A holder class that contains cache parameters.
+ */
+ public static class ImageCacheParams {
+ public int memCacheSize = DEFAULT_MEM_CACHE_SIZE;
+ public int compressQuality = DEFAULT_COMPRESS_QUALITY;
+ public boolean memoryCacheEnabled = DEFAULT_MEM_CACHE_ENABLED;
+ public boolean initDiskCacheOnCreate = DEFAULT_INIT_DISK_CACHE_ON_CREATE;
+
+
+ /**
+ * Sets the memory cache size based on a percentage of the max available
+ * VM memory. Eg. setting percent to 0.2 would set the memory cache to
+ * one fifth of the available memory. Throws
+ * {@link IllegalArgumentException} if percent is < 0.01 or > .8.
+ * memCacheSize is stored in kilobytes instead of bytes as this will
+ * eventually be passed to construct a LruCache which takes an int in
+ * its constructor.
+ *
+ * This value should be chosen carefully based on a number of factors
+ * Refer to the corresponding Android Training class for more
+ * discussion: http://developer.android.com/training/displaying-bitmaps/
+ *
+ * @param percent
+ * Percent of available app memory to use to size memory
+ * cache
+ */
+ public void setMemCacheSizePercent(float percent) {
+ if (percent < 0.01f || percent > 0.8f) {
+ throw new IllegalArgumentException(
+ "setMemCacheSizePercent - percent must be "
+ + "between 0.01 and 0.8 (inclusive)");
+ }
+ memCacheSize = Math.round(percent
+ * Runtime.getRuntime().maxMemory() / 1024);
+ }
+ }
+
+ /**
+ * @param candidate
+ * - Bitmap to check
+ * @param targetOptions
+ * - Options that have the out* value populated
+ * @return true if candidate
can be used for inBitmap re-use
+ * with targetOptions
+ */
+ @TargetApi(19)
+ private static boolean canUseForInBitmap(Bitmap candidate,
+ BitmapFactory.Options targetOptions) {
+ // BEGIN_INCLUDE(can_use_for_inbitmap)
+ if (!Utils.hasKitKat()) {
+ // On earlier versions, the dimensions must match exactly and the
+ // inSampleSize must be 1
+ return candidate.getWidth() == targetOptions.outWidth
+ && candidate.getHeight() == targetOptions.outHeight
+ && targetOptions.inSampleSize == 1;
+ }
+
+ // From Android 4.4 (KitKat) onward we can re-use if the byte size of
+ // the new bitmap
+ // is smaller than the reusable bitmap candidate allocation byte count.
+ int width = targetOptions.outWidth / targetOptions.inSampleSize;
+ int height = targetOptions.outHeight / targetOptions.inSampleSize;
+ int byteCount = width * height
+ * getBytesPerPixel(candidate.getConfig());
+ return byteCount <= candidate.getByteCount();
+ // END_INCLUDE(can_use_for_inbitmap)
+ }
+
+ /**
+ * Return the byte usage per pixel of a bitmap based on its configuration.
+ *
+ * @param config
+ * The bitmap configuration.
+ * @return The byte usage per pixel.
+ */
+ private static int getBytesPerPixel(Config config) {
+ if (config == Config.ARGB_8888) {
+ return 4;
+ } else if (config == Config.RGB_565) {
+ return 2;
+ } else if (config == Config.ARGB_4444) {
+ return 2;
+ } else if (config == Config.ALPHA_8) {
+ return 1;
+ }
+ return 1;
+ }
+
+ /**
+ * Get a usable cache directory (external if available, internal otherwise).
+ *
+ * @param context
+ * The context to use
+ * @param uniqueName
+ * A unique directory name to append to the cache dir
+ * @return The cache dir
+ */
+ public static File getDiskCacheDir(Context context, String uniqueName) {
+ // Check if media is mounted or storage is built-in, if so, try and use
+ // external cache dir
+ // otherwise use internal cache dir
+ final String cachePath = Environment.MEDIA_MOUNTED.equals(Environment
+ .getExternalStorageState()) || !isExternalStorageRemovable() ? getExternalCacheDir(
+ context).getPath()
+ : context.getCacheDir().getPath();
+
+ return new File(cachePath + File.separator + uniqueName);
+ }
+
+ /**
+ * A hashing method that changes a string (like a URL) into a hash suitable
+ * for using as a disk filename.
+ */
+ public static String hashKeyForDisk(String key) {
+ String cacheKey;
+ try {
+ final MessageDigest mDigest = MessageDigest.getInstance("MD5");
+ mDigest.update(key.getBytes());
+ cacheKey = bytesToHexString(mDigest.digest());
+ } catch (NoSuchAlgorithmException e) {
+ cacheKey = String.valueOf(key.hashCode());
+ }
+ return cacheKey;
+ }
+
+ private static String bytesToHexString(byte[] bytes) {
+ // http://stackoverflow.com/questions/332079
+ StringBuilder sb = new StringBuilder();
+ for (byte aByte : bytes) {
+ String hex = Integer.toHexString(0xFF & aByte);
+ if (hex.length() == 1) {
+ sb.append('0');
+ }
+ sb.append(hex);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Get the size in bytes of a bitmap in a BitmapDrawable. Note that from
+ * Android 4.4 (KitKat) onward this returns the allocated memory size of the
+ * bitmap which can be larger than the actual bitmap data byte count (in the
+ * case it was re-used).
+ *
+ * @param value
+ * @return size in bytes
+ */
+ @TargetApi(19)
+ public static int getBitmapSize(BitmapDrawable value) {
+ Bitmap bitmap = value.getBitmap();
+
+ // From KitKat onward use getAllocationByteCount() as allocated bytes
+ // can potentially be
+ // larger than bitmap byte count.
+// if (Utils.hasKitKat()) {
+// return bitmap.getAllocationByteCount();
+// }
+
+ if (Utils.hasHoneycombMR1()) {
+ return bitmap.getByteCount();
+ }
+
+ // Pre HC-MR1
+ return bitmap.getRowBytes() * bitmap.getHeight();
+ }
+
+ /**
+ * Check if external storage is built-in or removable.
+ *
+ * @return True if external storage is removable (like an SD card), false
+ * otherwise.
+ */
+ @TargetApi(VERSION_CODES.GINGERBREAD)
+ public static boolean isExternalStorageRemovable() {
+ if (Utils.hasGingerbread()) {
+ return Environment.isExternalStorageRemovable();
+ }
+ return true;
+ }
+
+ /**
+ * Get the external app cache directory.
+ *
+ * @param context
+ * The context to use
+ * @return The external cache dir
+ */
+ @TargetApi(VERSION_CODES.FROYO)
+ public static File getExternalCacheDir(Context context) {
+ if (Utils.hasFroyo()) {
+ return context.getExternalCacheDir();
+ }
+
+ // Before Froyo we need to construct the external cache dir ourselves
+ final String cacheDir = "/Android/data/" + context.getPackageName()
+ + "/cache/";
+ return new File(Environment.getExternalStorageDirectory().getPath()
+ + cacheDir);
+ }
+
+ /**
+ * Locate an existing instance of this Fragment or if not found, create and
+ * add it using FragmentManager.
+ *
+ * @param fm
+ * The FragmentManager manager to use.
+ * @return The existing instance of the Fragment or the new instance if just
+ * created.
+ */
+ private static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
+ // BEGIN_INCLUDE(find_create_retain_fragment)
+ // Check to see if we have retained the worker fragment.
+ RetainFragment mRetainFragment = (RetainFragment) fm
+ .findFragmentByTag(TAG);
+
+ // If not retained (or first time running), we need to create and add
+ // it.
+ if (mRetainFragment == null) {
+ mRetainFragment = new RetainFragment();
+ fm.beginTransaction().add(mRetainFragment, TAG)
+ .commitAllowingStateLoss();
+ }
+
+ return mRetainFragment;
+ // END_INCLUDE(find_create_retain_fragment)
+ }
+
+ /**
+ * A simple non-UI Fragment that stores a single Object and is retained over
+ * configuration changes. It will be used to retain the ImageCache object.
+ */
+ public static class RetainFragment extends Fragment {
+ private Object mObject;
+
+ /**
+ * Empty constructor as per the Fragment documentation
+ */
+ public RetainFragment() {
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Make sure this Fragment is retained over a configuration change
+ setRetainInstance(true);
+ }
+
+ /**
+ * Store a single object in this Fragment.
+ *
+ * @param object
+ * The object to store
+ */
+ public void setObject(Object object) {
+ mObject = object;
+ }
+
+ /**
+ * Get the stored object.
+ *
+ * @return The stored object
+ */
+ public Object getObject() {
+ return mObject;
+ }
+ }
+
+}
diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/ImageResizer.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/ImageResizer.java
new file mode 100644
index 0000000..1eb9f1d
--- /dev/null
+++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/ImageResizer.java
@@ -0,0 +1,291 @@
+package com.example.nanchen.aiyaschoolpush.im;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.ThumbnailUtils;
+import android.os.Build;
+import android.provider.MediaStore.Video.Thumbnails;
+
+import java.io.FileDescriptor;
+
+public class ImageResizer extends ImageWorker {
+ private static final String TAG = "ImageResizer";
+ protected int mImageWidth;
+ protected int mImageHeight;
+
+ /**
+ * Initialize providing a single target image size (used for both width and
+ * height);
+ *
+ * @param context
+ * @param imageWidth
+ * @param imageHeight
+ */
+ public ImageResizer(Context context, int imageWidth, int imageHeight) {
+ super(context);
+ setImageSize(imageWidth, imageHeight);
+ }
+
+ /**
+ * Initialize providing a single target image size (used for both width and
+ * height);
+ *
+ * @param context
+ * @param imageSize
+ */
+ public ImageResizer(Context context, int imageSize) {
+ super(context);
+ setImageSize(imageSize);
+ }
+
+ /**
+ * Set the target image width and height.
+ *
+ * @param width
+ * @param height
+ */
+ public void setImageSize(int width, int height) {
+ mImageWidth = width;
+ mImageHeight = height;
+ }
+
+ /**
+ * Set the target image size (width and height will be the same).
+ *
+ * @param size
+ */
+ public void setImageSize(int size) {
+ setImageSize(size, size);
+ }
+
+ /**
+ * The main processing method. This happens in a background task. In this
+ * case we are just sampling down the bitmap and returning it from a
+ * resource.
+ *
+ * @param resId
+ * @return
+ */
+ private Bitmap processBitmap(int resId) {
+// if (BuildConfig.DEBUG) {
+// Log.d(TAG, "processBitmap - " + resId);
+// }
+ return decodeSampledBitmapFromResource(mResources, resId, mImageWidth,
+ mImageHeight, getImageCache());
+ }
+
+ @Override
+ protected Bitmap processBitmap(Object data) {
+
+ String filePath=String.valueOf(data);
+ return ThumbnailUtils.createVideoThumbnail(filePath, Thumbnails.MICRO_KIND);
+ }
+
+ /**
+ * Decode and sample down a bitmap from resources to the requested width and
+ * height.
+ *
+ * @param res
+ * The resources object containing the image data
+ * @param resId
+ * The resource id of the image data
+ * @param reqWidth
+ * The requested width of the resulting bitmap
+ * @param reqHeight
+ * The requested height of the resulting bitmap
+ * @param cache
+ * The ImageCache used to find candidate bitmaps for use with
+ * inBitmap
+ * @return A bitmap sampled down from the original with the same aspect
+ * ratio and dimensions that are equal to or greater than the
+ * requested width and height
+ */
+ public static Bitmap decodeSampledBitmapFromResource(Resources res,
+ int resId, int reqWidth, int reqHeight, ImageCache cache) {
+
+ // BEGIN_INCLUDE (read_bitmap_dimensions)
+ // First decode with inJustDecodeBounds=true to check dimensions
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeResource(res, resId, options);
+
+ // Calculate inSampleSize
+ options.inSampleSize = calculateInSampleSize(options, reqWidth,
+ reqHeight);
+ // END_INCLUDE (read_bitmap_dimensions)
+
+ // If we're running on Honeycomb or newer, try to use inBitmap
+ if (Utils.hasHoneycomb()) {
+ addInBitmapOptions(options, cache);
+ }
+
+ // Decode bitmap with inSampleSize set
+ options.inJustDecodeBounds = false;
+ return BitmapFactory.decodeResource(res, resId, options);
+ }
+
+ /**
+ * Decode and sample down a bitmap from a file to the requested width and
+ * height.
+ *
+ * @param filename
+ * The full path of the file to decode
+ * @param reqWidth
+ * The requested width of the resulting bitmap
+ * @param reqHeight
+ * The requested height of the resulting bitmap
+ * @param cache
+ * The ImageCache used to find candidate bitmaps for use with
+ * inBitmap
+ * @return A bitmap sampled down from the original with the same aspect
+ * ratio and dimensions that are equal to or greater than the
+ * requested width and height
+ */
+ public static Bitmap decodeSampledBitmapFromFile(String filename,
+ int reqWidth, int reqHeight, ImageCache cache) {
+
+ // First decode with inJustDecodeBounds=true to check dimensions
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(filename, options);
+
+ // Calculate inSampleSize
+ options.inSampleSize = calculateInSampleSize(options, reqWidth,
+ reqHeight);
+
+ // If we're running on Honeycomb or newer, try to use inBitmap
+ if (Utils.hasHoneycomb()) {
+ addInBitmapOptions(options, cache);
+ }
+
+ // Decode bitmap with inSampleSize set
+ options.inJustDecodeBounds = false;
+ return BitmapFactory.decodeFile(filename, options);
+ }
+
+ /**
+ * Decode and sample down a bitmap from a file input stream to the requested
+ * width and height.
+ *
+ * @param fileDescriptor
+ * The file descriptor to read from
+ * @param reqWidth
+ * The requested width of the resulting bitmap
+ * @param reqHeight
+ * The requested height of the resulting bitmap
+ * @param cache
+ * The ImageCache used to find candidate bitmaps for use with
+ * inBitmap
+ * @return A bitmap sampled down from the original with the same aspect
+ * ratio and dimensions that are equal to or greater than the
+ * requested width and height
+ */
+ public static Bitmap decodeSampledBitmapFromDescriptor(
+ FileDescriptor fileDescriptor, int reqWidth, int reqHeight,
+ ImageCache cache) {
+
+ // First decode with inJustDecodeBounds=true to check dimensions
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
+
+ // Calculate inSampleSize
+ options.inSampleSize = calculateInSampleSize(options, reqWidth,
+ reqHeight);
+
+ // Decode bitmap with inSampleSize set
+ options.inJustDecodeBounds = false;
+
+ // If we're running on Honeycomb or newer, try to use inBitmap
+ if (Utils.hasHoneycomb()) {
+ addInBitmapOptions(options, cache);
+ }
+
+ return BitmapFactory
+ .decodeFileDescriptor(fileDescriptor, null, options);
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ private static void addInBitmapOptions(BitmapFactory.Options options,
+ ImageCache cache) {
+ // BEGIN_INCLUDE(add_bitmap_options)
+ // inBitmap only works with mutable bitmaps so force the decoder to
+ // return mutable bitmaps.
+ options.inMutable = true;
+
+ if (cache != null) {
+ // Try and find a bitmap to use for inBitmap
+ Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
+
+ if (inBitmap != null) {
+ options.inBitmap = inBitmap;
+ }
+ }
+ // END_INCLUDE(add_bitmap_options)
+ }
+
+ /**
+ * Calculate an inSampleSize for use in a
+ * {@link BitmapFactory.Options} object when decoding
+ * bitmaps using the decode* methods from
+ * {@link BitmapFactory}. This implementation calculates
+ * the closest inSampleSize that is a power of 2 and will result in the
+ * final decoded bitmap having a width and height equal to or larger than
+ * the requested width and height.
+ *
+ * @param options
+ * An options object with out* params already populated (run
+ * through a decode* method with inJustDecodeBounds==true
+ * @param reqWidth
+ * The requested width of the resulting bitmap
+ * @param reqHeight
+ * The requested height of the resulting bitmap
+ * @return The value to be used for inSampleSize
+ */
+ public static int calculateInSampleSize(BitmapFactory.Options options,
+ int reqWidth, int reqHeight) {
+ // BEGIN_INCLUDE (calculate_sample_size)
+ // Raw height and width of image
+ final int height = options.outHeight;
+ final int width = options.outWidth;
+ int inSampleSize = 1;
+
+ if (height > reqHeight || width > reqWidth) {
+
+ final int halfHeight = height / 2;
+ final int halfWidth = width / 2;
+
+ // Calculate the largest inSampleSize value that is a power of 2 and
+ // keeps both
+ // height and width larger than the requested height and width.
+ while ((halfHeight / inSampleSize) > reqHeight
+ && (halfWidth / inSampleSize) > reqWidth) {
+ inSampleSize *= 2;
+ }
+
+ // This offers some additional logic in case the image has a strange
+ // aspect ratio. For example, a panorama may have a much larger
+ // width than height. In these cases the total pixels might still
+ // end up being too large to fit comfortably in memory, so we should
+ // be more aggressive with sample down the image (=larger
+ // inSampleSize).
+
+ long totalPixels = width * height / inSampleSize;
+
+ // Anything more than 2x the requested pixels we'll sample down
+ // further
+ final long totalReqPixelsCap = reqWidth * reqHeight * 2;
+
+ while (totalPixels > totalReqPixelsCap) {
+ inSampleSize *= 2;
+ totalPixels /= 2;
+ }
+ }
+ return inSampleSize;
+ // END_INCLUDE (calculate_sample_size)
+ }
+
+}
diff --git a/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/ImageWorker.java b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/ImageWorker.java
new file mode 100644
index 0000000..d340f3b
--- /dev/null
+++ b/文档/Android/app/src/main/java/com/example/nanchen/aiyaschoolpush/im/ImageWorker.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.nanchen.aiyaschoolpush.im;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.TransitionDrawable;
+import android.os.AsyncTask;
+import android.support.v4.app.FragmentManager;
+import android.widget.ImageView;
+
+import com.example.nanchen.aiyaschoolpush.App;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ * This class wraps up completing some arbitrary long running work when loading a bitmap to an
+ * ImageView. It handles things like using a memory and disk cache, running the work in a background
+ * thread and setting a placeholder image.
+ */
+public abstract class ImageWorker {
+ private static final String TAG = "ImageWorker";
+ private static final int FADE_IN_TIME = 200;
+
+ private ImageCache mImageCache;
+ private Bitmap mLoadingBitmap;
+ private boolean mFadeInBitmap = true;
+ private boolean mExitTasksEarly = false;
+ protected boolean mPauseWork = false;
+ private final Object mPauseWorkLock = new Object();
+
+ protected Resources mResources;
+
+ private static final int MESSAGE_CLEAR = 0;
+ private static final int MESSAGE_INIT_DISK_CACHE = 1;
+ private static final int MESSAGE_FLUSH = 2;
+ private static final int MESSAGE_CLOSE = 3;
+
+ public static final Executor DUAL_THREAD_EXECUTOR = Executors
+ .newFixedThreadPool(2);
+
+ protected ImageWorker(Context context) {
+ mResources = context.getResources();
+ }
+
+ /**
+ * Load an image specified by the data parameter into an ImageView (override
+ * {@link ImageWorker#processBitmap(Object)} to define the processing logic). A memory and
+ * disk cache will be used if an {@link ImageCache} has been added using
+ * {@link ImageWorker#addImageCache(FragmentManager, ImageCache.ImageCacheParams)}. If the
+ * image is found in the memory cache, it is set immediately, otherwise an {@link AsyncTask}
+ * will be created to asynchronously load the bitmap.
+ *
+ * @param data The URL of the image to download.
+ * @param imageView The ImageView to bind the downloaded image to.
+ */
+ public void loadImage(Object data, ImageView imageView) {
+ if (data == null) {
+ return;
+ }
+
+ BitmapDrawable value = null;
+
+ if (mImageCache != null) {
+ value = mImageCache.getBitmapFromMemCache(String.valueOf(data));
+ }
+
+ if (value != null) {
+ // Bitmap found in memory cache
+ imageView.setImageDrawable(value);
+ } else if (cancelPotentialWork(data, imageView)) {
+ //BEGIN_INCLUDE(execute_background_task)
+ final BitmapWorkerTask task = new BitmapWorkerTask(data, imageView);
+ final AsyncDrawable asyncDrawable =
+ new AsyncDrawable(mResources, mLoadingBitmap, task);
+ imageView.setImageDrawable(asyncDrawable);
+
+ // NOTE: This uses a custom version of AsyncTask that has been pulled from the
+ // framework and slightly modified. Refer to the docs at the top of the class
+ // for more info on what was changed.
+ task.executeOnExecutor(DUAL_THREAD_EXECUTOR);
+ //END_INCLUDE(execute_background_task)
+ }
+ }
+
+ /**
+ * Set placeholder bitmap that shows when the the background thread is running.
+ *
+ * @param bitmap
+ */
+ public void setLoadingImage(Bitmap bitmap) {
+ mLoadingBitmap = bitmap;
+ }
+
+ /**
+ * Set placeholder bitmap that shows when the the background thread is running.
+ *
+ * @param resId
+ */
+ public void setLoadingImage(int resId) {
+ mLoadingBitmap = BitmapFactory.decodeResource(mResources, resId);
+ }
+
+ /**
+ * Adds an {@link ImageCache} to this {@link ImageWorker} to handle disk and memory bitmap
+ * caching.
+ * @param fragmentManager
+ * @param cacheParams The cache parameters to use for the image cache.
+ */
+ public void addImageCache(FragmentManager fragmentManager,
+ ImageCache.ImageCacheParams cacheParams) {
+ ImageCache.ImageCacheParams mImageCacheParams = cacheParams;
+ mImageCache = ImageCache.getInstance(fragmentManager, mImageCacheParams);
+ new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);
+ }
+
+
+ /**
+ * If set to true, the image will fade-in once it has been loaded by the background thread.
+ */
+ public void setImageFadeIn(boolean fadeIn) {
+ mFadeInBitmap = fadeIn;
+ }
+
+ public void setExitTasksEarly(boolean exitTasksEarly) {
+ mExitTasksEarly = exitTasksEarly;
+ setPauseWork(false);
+ }
+
+ /**
+ * Subclasses should override this to define any processing or work that must happen to produce
+ * the final bitmap. This will be executed in a background thread and be long running. For
+ * example, you could resize a large bitmap here, or pull down an image from the network.
+ *
+ * @param data The data to identify which image to process, as provided by
+ * {@link ImageWorker#loadImage(Object, ImageView)}
+ * @return The processed bitmap
+ */
+ protected abstract Bitmap processBitmap(Object data);
+
+ /**
+ * @return The {@link ImageCache} object currently being used by this ImageWorker.
+ */
+ protected ImageCache getImageCache() {
+ return mImageCache;
+ }
+
+ /**
+ * Cancels any pending work attached to the provided ImageView.
+ * @param imageView
+ */
+ public static void cancelWork(ImageView imageView) {
+ final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+ if (bitmapWorkerTask != null) {
+ bitmapWorkerTask.cancel(true);
+// if (BuildConfig.DEBUG) {
+// final Object bitmapData = bitmapWorkerTask.mData;
+// Log.d(TAG, "cancelWork - cancelled work for " + bitmapData);
+// }
+ }
+ }
+
+ /**
+ * Returns true if the current work has been canceled or if there was no work in
+ * progress on this image view.
+ * Returns false if the work in progress deals with the same data. The work is not
+ * stopped in that case.
+ */
+ public static boolean cancelPotentialWork(Object data, ImageView imageView) {
+ //BEGIN_INCLUDE(cancel_potential_work)
+ final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+ if (bitmapWorkerTask != null) {
+ final Object bitmapData = bitmapWorkerTask.mData;
+ if (bitmapData == null || !bitmapData.equals(data)) {
+ bitmapWorkerTask.cancel(true);
+// if (BuildConfig.DEBUG) {
+// Log.d(TAG, "cancelPotentialWork - cancelled work for " + data);
+// }
+ } else {
+ // The same work is already in progress.
+ return false;
+ }
+ }
+ return true;
+ //END_INCLUDE(cancel_potential_work)
+ }
+
+ /**
+ * @param imageView Any imageView
+ * @return Retrieve the currently active work task (if any) associated with this imageView.
+ * null if there is no such task.
+ */
+ private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
+ if (imageView != null) {
+ final Drawable drawable = imageView.getDrawable();
+ if (drawable instanceof AsyncDrawable) {
+ final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
+ return asyncDrawable.getBitmapWorkerTask();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * The actual AsyncTask that will asynchronously process the image.
+ */
+ private class BitmapWorkerTask extends AsyncTask {
+ private Object mData;
+ private final WeakReference imageViewReference;
+
+ public BitmapWorkerTask(Object data, ImageView imageView) {
+ mData = data;
+ imageViewReference = new WeakReference(imageView);
+ }
+
+ /**
+ * Background processing.
+ */
+ @Override
+ protected BitmapDrawable doInBackground(Void... params) {
+ //BEGIN_INCLUDE(load_bitmap_in_background)
+// if (BuildConfig.DEBUG) {
+// Log.d(TAG, "doInBackground - starting work");
+// }
+
+ final String dataString = String.valueOf(mData);
+ Bitmap bitmap = null;
+ BitmapDrawable drawable = null;
+
+ // Wait here if work is paused and the task is not cancelled
+ synchronized (mPauseWorkLock) {
+ while (mPauseWork && !isCancelled()) {
+ try {
+ mPauseWorkLock.wait();
+ } catch (InterruptedException e) {}
+ }
+ }
+
+
+
+ // If the bitmap was not found in the cache and this task has not been cancelled by
+ // another thread and the ImageView that was originally bound to this task is still
+ // bound back to this task and our "exit early" flag is not set, then call the main
+ // process method (as implemented by a subclass)
+ if (bitmap == null && !isCancelled() && getAttachedImageView() != null
+ && !mExitTasksEarly) {
+ bitmap = processBitmap(mData);
+ }
+
+ // If the bitmap was processed and the image cache is available, then add the processed
+ // bitmap to the cache for future use. Note we don't check if the task was cancelled
+ // here, if it was, and the thread is still running, we may as well add the processed
+ // bitmap to our cache as it might be used again in the future
+ if (bitmap != null) {
+ if (Utils.hasHoneycomb()) {
+ // Running on Honeycomb or newer, so wrap in a standard BitmapDrawable
+ drawable = new BitmapDrawable(mResources, bitmap);
+ } else {
+ // Running on Gingerbread or older, so wrap in a RecyclingBitmapDrawable
+ // which will recycle automagically
+ drawable = new RecyclingBitmapDrawable(mResources, bitmap);
+ }
+
+ if (mImageCache != null) {
+ mImageCache.addBitmapToCache(dataString, drawable);
+ }
+ }
+
+// if (BuildConfig.DEBUG) {
+// Log.d(TAG, "doInBackground - finished work");
+// }
+
+ return drawable;
+ //END_INCLUDE(load_bitmap_in_background)
+ }
+
+ /**
+ * Once the image is processed, associates it to the imageView
+ */
+ @Override
+ protected void onPostExecute(BitmapDrawable value) {
+ //BEGIN_INCLUDE(complete_background_work)
+ // if cancel was called on this task or the "exit early" flag is set then we're done
+ if (isCancelled() || mExitTasksEarly) {
+ value = null;
+ }
+
+ final ImageView imageView = getAttachedImageView();
+ if (value != null && imageView != null) {
+// if (BuildConfig.DEBUG) {
+// Log.d(TAG, "onPostExecute - setting bitmap");
+// }
+ setImageDrawable(imageView, value);
+ }
+ //END_INCLUDE(complete_background_work)
+ }
+
+ @Override
+ protected void onCancelled(BitmapDrawable value) {
+ super.onCancelled(value);
+ synchronized (mPauseWorkLock) {
+ mPauseWorkLock.notifyAll();
+ }
+ }
+
+ /**
+ * Returns the ImageView associated with this task as long as the ImageView's task still
+ * points to this task as well. Returns null otherwise.
+ */
+ private ImageView getAttachedImageView() {
+ final ImageView imageView = imageViewReference.get();
+ final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+ if (this == bitmapWorkerTask) {
+ return imageView;
+ }
+
+ return null;
+ }
+ }
+
+ /**
+ * A custom Drawable that will be attached to the imageView while the work is in progress.
+ * Contains a reference to the actual worker task, so that it can be stopped if a new binding is
+ * required, and makes sure that only the last started worker process can bind its result,
+ * independently of the finish order.
+ */
+ private static class AsyncDrawable extends BitmapDrawable {
+ private final WeakReference bitmapWorkerTaskReference;
+
+ public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
+ super(res, bitmap);
+ bitmapWorkerTaskReference =
+ new WeakReference(bitmapWorkerTask);
+ }
+
+ public BitmapWorkerTask getBitmapWorkerTask() {
+ return bitmapWorkerTaskReference.get();
+ }
+ }
+
+ /**
+ * Called when the processing is complete and the final drawable should be
+ * set on the ImageView.
+ *
+ * @param imageView
+ * @param drawable
+ */
+ private void setImageDrawable(ImageView imageView, Drawable drawable) {
+ if (mFadeInBitmap) {
+ // Transition drawable with a transparent drawable and the final drawable
+ final TransitionDrawable td =
+ new TransitionDrawable(new Drawable[] {
+ new ColorDrawable(App.getAppContext().getResources().getColor(android.R.color.transparent)),
+ drawable
+ });
+ // Set background to loading bitmap
+ imageView.setBackgroundDrawable(
+ new BitmapDrawable(mResources, mLoadingBitmap));
+
+ imageView.setImageDrawable(td);
+ td.startTransition(FADE_IN_TIME);
+ } else {
+ imageView.setImageDrawable(drawable);
+ }
+ }
+
+ /**
+ * Pause any ongoing background work. This can be used as a temporary
+ * measure to improve performance. For example background work could
+ * be paused when a ListView or GridView is being scrolled using a
+ * {@link android.widget.AbsListView.OnScrollListener} to keep
+ * scrolling smooth.
+ *
+ * If work is paused, be sure setPauseWork(false) is called again
+ * before your fragment or activity is destroyed (for example during
+ * {@link android.app.Activity#onPause()}), or there is a risk the
+ * background thread will never finish.
+ */
+ public void setPauseWork(boolean pauseWork) {
+ synchronized (mPauseWorkLock) {
+ mPauseWork = pauseWork;
+ if (!mPauseWork) {
+ mPauseWorkLock.notifyAll();
+ }
+ }
+ }
+
+ protected class CacheAsyncTask extends AsyncTask