Android 7.1 电子邮件中不识别网址和邮箱地址

Android Email显示邮件内容是使用webview来显示,对于电话号码/网址/邮箱地址等能否正常解析,全部取决于webview的解析规则,当前我们的解析规则是和Nexus相同的。我们 Follow Google Original Behavior,不做修改了。 

提供一个实现思路: 

在传给webview之前,把数据中所有可能的电话号码/网址/邮箱地址添加上html标记,类似<a href></a>这种。

diff --git a/src/com/android/mail/browse/WebViewContextMenu.java b/src/com/android/mail/browse/WebViewContextMenu.java
index b81a3be..87a2ed2 100644
--- a/src/com/android/mail/browse/WebViewContextMenu.java
+++ b/src/com/android/mail/browse/WebViewContextMenu.java
@@ -42,6 +42,7 @@ import java.io.UnsupportedEncodingException;
 import java.net.URLDecoder;
 import java.net.URLEncoder;
 import java.nio.charset.Charset;
+import android.util.Log;
 
 /**
  * <p>Handles display and behavior of the context menu for known actionable content in WebViews.
@@ -245,6 +246,7 @@ public class WebViewContextMenu implements OnCreateContextMenuListener,
 
         // Show the correct menu group
         String extra = result.getExtra();
+        Log.d("zhuwenwu", "onCreateContextMenu --> extra = " + extra);
         menu.setGroupVisible(R.id.PHONE_MENU, type == WebView.HitTestResult.PHONE_TYPE);
         menu.setGroupVisible(R.id.EMAIL_MENU, type == WebView.HitTestResult.EMAIL_TYPE);
         menu.setGroupVisible(R.id.GEO_MENU, type == WebView.HitTestResult.GEO_TYPE);
@@ -277,6 +279,8 @@ public class WebViewContextMenu implements OnCreateContextMenuListener,
                 }
 
                 menu.setHeaderTitle(decodedPhoneExtra);
+Log.d("zhuwenwu", "281 decodedPhoneExtra: " +decodedPhoneExtra);
+
                 // Dial
                 final MenuItem dialMenuItem =
                         menu.findItem(getMenuResIdForMenuType(MenuType.DIAL_MENU));
@@ -318,6 +322,7 @@ public class WebViewContextMenu implements OnCreateContextMenuListener,
                 Analytics.getInstance().sendEvent(
                         CATEGORY_WEB_CONTEXT_MENU, ACTION_LONG_PRESS, "email", 0);
                 menu.setHeaderTitle(extra);
+Log.d("zhuwenwu", "324 extra: " +extra);
                 final Intent mailtoIntent =
                         new Intent(Intent.ACTION_VIEW, Uri.parse(WebView.SCHEME_MAILTO + extra));
                 menu.findItem(getMenuResIdForMenuType(MenuType.EMAIL_CONTACT_MENU))
@@ -329,6 +334,7 @@ public class WebViewContextMenu implements OnCreateContextMenuListener,
                 Analytics.getInstance().sendEvent(
                         CATEGORY_WEB_CONTEXT_MENU, ACTION_LONG_PRESS, "geo", 0);
                 menu.setHeaderTitle(extra);
+Log.d("zhuwenwu", "336 extra: " +extra);
                 String geoExtra = "";
                 try {
                     geoExtra = URLEncoder.encode(extra, Charset.defaultCharset().name());
@@ -373,8 +379,9 @@ public class WebViewContextMenu implements OnCreateContextMenuListener,
         // SRC_ANCHOR_TYPE or the url would be specified in the extra.  We don't need to
         // call requestFocusNodeHref().  If we wanted to handle UNKNOWN HitTestResults, we
         // would.  With this knowledge, we can just set the title
+        Log.d("zhuwenwu", "setupAnchorMenu --> extra = " + extra);
         menu.setHeaderTitle(extra);
-
+Log.d("zhuwenwu", "382 extra: " +extra);
         menu.findItem(getMenuResIdForMenuType(MenuType.COPY_LINK_MENU)).
                 setOnMenuItemClickListener(new Copy(extra, "copy_link"));
 
diff --git a/src/com/android/mail/ui/HtmlConversationTemplates.java b/src/com/android/mail/ui/HtmlConversationTemplates.java
index 124cb83..d95110e 100644
--- a/src/com/android/mail/ui/HtmlConversationTemplates.java
+++ b/src/com/android/mail/ui/HtmlConversationTemplates.java
@@ -30,6 +30,12 @@ import com.google.common.annotations.VisibleForTesting;
 import java.util.Locale;
 import java.util.regex.Pattern;
 
+import android.util.Patterns;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import android.util.Log;
+
+
 /**
  * Renders data into very simple string-substitution HTML templates for conversation view.
  */
@@ -135,7 +141,12 @@ public class HtmlConversationTemplates extends AbstractHtmlTemplates {
         final String showImagesClass = safeForImages ? "mail-show-images" : "";
 
         String body = message.getBodyAsHtml();
-
+        // Modify bug 1876 recognise phonenum and mark it by qidongran start
+        body = body.replaceAll("&#64;", "@");
+        body=addWebMark(body);
+        body=addTelMark(body);
+        body=addEmailMark(body);
+        // Modify bug 1876 recognise phonenum and mark it by qidongran end
         /* Work around a WebView bug (5522414) in setBlockNetworkImage that causes img onload event
          * handlers to fire before an image is loaded.
          * WebView will report bad dimensions when revealing inline images with absolute URLs, but
@@ -214,4 +225,288 @@ public class HtmlConversationTemplates extends AbstractHtmlTemplates {
 
         return emit();
     }
+    // Modify bug 1876 by qidongran start
+    private String replacedString = "";
+    private String PHONE_HIGHLIGHT_ELEMENT = "tel:";
+    private String EMAIL_HIGHLIGHT_ELEMENT = "mailto:";
+
+    private String addWebMark(String text) {
+        replacedString = text;
+        String MATCH_WEB_URL = "www\\.[^(\\s|\"|<|')]*|[a-zA-z]+://[^(\\s|\"|<|')]*";
+        Pattern pattern = Pattern.compile(MATCH_WEB_URL);
+        Matcher matcher = pattern.matcher(replacedString);
+        try {
+            Log.d(TAG, "zhuwenwu1 addWebMark --> text:" + text);
+            replacedString = replaceWeb(matcher);
+Log.d(TAG, "zhuwenwu1 addWebMark --> replacedString:" + replacedString);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return replacedString;
+    }
+
+    private String replaceWeb(Matcher matcher) {
+        String keyString = "";
+        String result = replacedString;
+        StringBuffer sb = new StringBuffer("");
+        while (matcher.find()) {
+            keyString = matcher.group();
+            Log.d(TAG, "zhuwenwu matcher.find():" + keyString);
+            // the character in front og keystring is "." or "/" or "@"... ignore
+            if (matcher.start() - 1 >= 0) {
+                char keyChar = result.charAt(matcher.start() - 1);
+                if (keyChar == 45 || keyChar == 47 || keyChar == 61
+                        || keyChar == 63 || keyChar == 95
+                        || (48 <= keyChar && keyChar <= 57)
+                        || (35 <= keyChar && keyChar <= 38)) {
+                    matcher.appendReplacement(sb, "$0");
+                    continue;
+                }
+            }
+            int end = matcher.start();
+            int start = end - 8;
+            if (start < 0) {
+                start = end - 7;
+            }
+            if (start >= 0) {
+                String urlStr = result.substring(start, end);
+                if (urlStr.contains("http") || urlStr.contains("Http")
+                        || urlStr.contains("HTTP")
+                        || urlStr.contains("ftp") || urlStr.contains("Ftp")
+                        || urlStr.contains("FTP")
+                        || urlStr.contains("rtsp")
+                        || urlStr.contains("Rtsp")
+                        || urlStr.contains("RTSP")) {
+                    matcher.appendReplacement(sb, "$0");
+                    continue;
+                }
+            }
+            // the character behind keystring is "."|or "@" or "A-z" ignore
+            if (matcher.end() != result.length()) {
+                char keyChar = result.charAt(matcher.end());
+                if ((64 <= keyChar && keyChar <= 122) || keyChar == 46
+                        || (48 <= keyChar && keyChar <= 57)) {
+                    matcher.appendReplacement(sb, "$0");
+                    continue;
+                }
+            }
+
+            int endPhone = matcher.start();
+            int startPhone = endPhone - 7;
+            if (startPhone > 0) {
+                String emailStr = result.substring(startPhone, endPhone);
+                // contain "href" ignore
+                if (emailStr.contains("href")) {
+                    matcher.appendReplacement(sb, "$0");
+                    continue;
+                } else {
+                    startPhone = endPhone + keyString.length() + 7;
+                    if (startPhone > result.length()) {
+                        startPhone = result.length();
+                    }
+                    emailStr = result.substring(endPhone, startPhone);
+                    // contain "</a>" ignore
+                    if (emailStr.contains("</a>")) {
+                        matcher.appendReplacement(sb, "$0");
+                        continue;
+                    }
+                }
+            }
+
+            // contain keystring ignore
+            int endStr = matcher.start() - 2;
+            int startStr = endStr - keyString.length();
+            if (startStr > 0) {
+                String webStr = result.substring(startStr, endStr);
+                if (keyString.contains(webStr)) {
+                    matcher.appendReplacement(sb, "$0");
+                    continue;
+                }
+            }
+            if(keyString.startsWith("www")){
+                keyString = "http://" + keyString;
+            }
+            String replacement = String.format("<a href=\"%s\">%s</a>", keyString, keyString);
+            Log.d(TAG, "zhuwenwu replacement:" + replacement);
+            matcher.appendReplacement(sb, replacement);
+        }
+        matcher.appendTail(sb);
+        return sb.toString();
+    }
+
+    private String addEmailMark(String text) {
+        replacedString = text;
+        String replaceElement = "";
+        String MATCH_EMAIL_URL = "[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[\\w](?:[\\w-]*[\\w])?";
+        Pattern pattern = Pattern.compile(MATCH_EMAIL_URL);
+        Matcher matcher = pattern.matcher(replacedString);
+        replaceElement = EMAIL_HIGHLIGHT_ELEMENT;
+        try {
+            Log.d(TAG, "zhuwenwu addEmailMark --> text:" + text);
+            replacedString = replaceKeyString(matcher, replaceElement);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return replacedString;
+    }
+
+    private String addTelMark(String text) {
+        replacedString = text;
+        String replaceElement = "";
+        Pattern pattern = Pattern
+                .compile("(((13[0-9])|(147)|(145)|(15[^4,\\D])|(173)|(17[6-8])|(18[0-9]))\\d{8})"
+                        + "|"
+                        + "(((\\d{4}|\\d{3})-(\\d{7,8})-(\\d{4}|\\d{3}|\\d{2}|\\d{1}))|((\\d{4}|\\d{3})-(\\d{7,8}))|((\\d{7,8})-(\\d{4}|\\d{3}|\\d{2}|\\d{1}))|(\\d{7,8}))");
+        Matcher matcher = pattern.matcher(replacedString);
+        replaceElement = PHONE_HIGHLIGHT_ELEMENT;
+        try {
+            Log.d(TAG, "zhuwenwu addTelMark --> text:" + text);
+            replacedString = replaceKeyString(matcher, replaceElement);
+        } catch (Exception e) {
+            LogUtils.v(LogUtils.TAG, e, "Did not add tel mark");
+        }
+
+        return replacedString;
+    }
+
+    /**
+     * replace the key String
+     *
+     * @return replacedString text has been replaced
+     */
+    private String replaceKeyString(Matcher matcher, String replaceElement) {
+        String keyString = "";
+        String result = replacedString;
+        StringBuffer sb = new StringBuffer("");
+        while (matcher.find()) {
+            keyString = matcher.group();
+            Log.d(TAG, "zhuwenwu matcher.find():" + keyString);
+            if (PHONE_HIGHLIGHT_ELEMENT.equals(replaceElement)) {
+                // the character in front og keystring is "." or "/" or "@"... ignore
+                if (matcher.start() - 1 >= 0) {
+                    char keyChar = result.charAt(matcher.start() - 1);
+                    if (keyChar == 45 || keyChar == 47 || keyChar == 61
+                            || keyChar == 63 || keyChar == 95
+                            || (48 <= keyChar && keyChar <= 57)
+                            || (35 <= keyChar && keyChar <= 38)) {
+                        matcher.appendReplacement(sb, "$0");
+                        continue;
+                    }
+                }
+                int end = matcher.start();
+                int start = end - 8;
+                if (start < 0) {
+                    start = end - 7;
+                }
+                if (start >= 0) {
+                    String urlStr = result.substring(start, end);
+                    if (urlStr.contains("http") || urlStr.contains("Http")
+                            || urlStr.contains("HTTP")
+                            || urlStr.contains("ftp") || urlStr.contains("Ftp")
+                            || urlStr.contains("FTP")
+                            || urlStr.contains("rtsp")
+                            || urlStr.contains("Rtsp")
+                            || urlStr.contains("RTSP")) {
+                        matcher.appendReplacement(sb, "$0");
+                        continue;
+                    }
+                }
+                // the character behind keystring is "."|or "@" or "A-z" ignore
+                if (matcher.end() != result.length()) {
+                    char keyChar = result.charAt(matcher.end());
+                    if ((64 <= keyChar && keyChar <= 122) || keyChar == 46
+                            || (48 <= keyChar && keyChar <= 57)) {
+                        matcher.appendReplacement(sb, "$0");
+                        continue;
+                    }
+                }
+
+                int endPhone = matcher.start();
+                int startPhone = endPhone - 4;
+                String emailStr = result.substring(startPhone, endPhone);
+                // contain "tel" ignore
+                if (emailStr.contains("tel")) {
+                    matcher.appendReplacement(sb, "$0");
+                    continue;
+                }
+                // contain keystring ignore
+                int endStr = matcher.start() - 2;
+                int startStr = endStr - keyString.length();
+                if (startStr > 0) {
+                    String webStr = result.substring(startStr, endStr);
+                    if (keyString.contains(webStr)) {
+                        matcher.appendReplacement(sb, "$0");
+                        continue;
+                    }
+                }
+            } else if (EMAIL_HIGHLIGHT_ELEMENT.equals(replaceElement)) {
+                // the character in front og keystring is "." or "/" or "@"... ignore
+                if (matcher.start() - 1 >= 0) {
+                    char keyChar = result.charAt(matcher.start() - 1);
+                    if (keyChar == 45 || keyChar == 47 || keyChar == 61
+                            || keyChar == 63 || keyChar == 95
+                            || (48 <= keyChar && keyChar <= 57)
+                            || (35 <= keyChar && keyChar <= 38)) {
+                        matcher.appendReplacement(sb, "$0");
+                        continue;
+                    }
+                }
+                int end = matcher.start();
+                int start = end - 8;
+                if (start < 0) {
+                    start = end - 7;
+                }
+                if (start >= 0) {
+                    String urlStr = result.substring(start, end);
+                    if (urlStr.contains("http") || urlStr.contains("Http")
+                            || urlStr.contains("HTTP")
+                            || urlStr.contains("ftp") || urlStr.contains("Ftp")
+                            || urlStr.contains("FTP")
+                            || urlStr.contains("rtsp")
+                            || urlStr.contains("Rtsp")
+                            || urlStr.contains("RTSP")) {
+                        matcher.appendReplacement(sb, "$0");
+                        continue;
+                    }
+                }
+                // the character behind keystring is "."|or "@" or "A-z" ignore
+                if (matcher.end() != result.length()) {
+                    char keyChar = result.charAt(matcher.end());
+                    if ((64 <= keyChar && keyChar <= 122) || keyChar == 46
+                            || (48 <= keyChar && keyChar <= 57)) {
+                        matcher.appendReplacement(sb, "$0");
+                        continue;
+                    }
+                }
+
+                int endPhone = matcher.start();
+                int startPhone = endPhone - 7;
+                String emailStr = result.substring(startPhone, endPhone);
+                // contain "mailto" ignore
+                if (emailStr.contains("mailto")) {
+                    matcher.appendReplacement(sb, "$0");
+                    continue;
+                }
+                // contain keystring ignore
+                int endStr = matcher.start() - 2;
+                int startStr = endStr - keyString.length();
+                if (startStr > 0) {
+                    String webStr = result.substring(startStr, endStr);
+                    if (keyString.contains(webStr)) {
+                        matcher.appendReplacement(sb, "$0");
+                        continue;
+                    }
+                }
+            }
+            String replacement = String.format("<a href=\"%s\">%s</a>", replaceElement + keyString,
+                    keyString);
+            Log.d(TAG, "zhuwenwu replacement:" + replacement);
+            matcher.appendReplacement(sb, replacement);
+            // LogUtils.i("qidongran", "sb:" + sb);
+        }
+        matcher.appendTail(sb);
+        return sb.toString();
+    }
+    // Modify bug 1876 by qidongran end
 }
diff --git a/src/com/android/mail/utils/HtmlSanitizer.java b/src/com/android/mail/utils/HtmlSanitizer.java
index ca831df..7a46db6 100644
--- a/src/com/android/mail/utils/HtmlSanitizer.java
+++ b/src/com/android/mail/utils/HtmlSanitizer.java
@@ -125,7 +125,7 @@ public final class HtmlSanitizer {
      * Disallow the "cid:" url on links. Do allow "mailto:" urls to support sending mail.
      */
     private static final AttributePolicy A_HREF_PROTOCOLS =
-            new FilterUrlByProtocolAttributePolicy(ImmutableList.of("mailto", "http", "https"));
+            new FilterUrlByProtocolAttributePolicy(ImmutableList.of("mailto", "http", "https", "tel", "sms"));
 
     /**
      * Disallow the "mailto:" url on images so that "Show pictures" can't be used to start composing
@@ -189,7 +189,7 @@ public final class HtmlSanitizer {
      */
     private static final PolicyFactory POLICY_DEFINITION = new HtmlPolicyBuilder()
             .allowAttributes("dir").matching(true, "ltr", "rtl").globally()
-            .allowUrlProtocols("cid", "http", "https", "mailto")
+            .allowUrlProtocols("cid", "http", "https", "mailto", "tel", "sms") /* @}*/
             .allowStyling(CssSchema.union(CssSchema.DEFAULT, ADDITIONAL_CSS))
             .disallowTextIn("applet", "frameset", "object", "script", "style", "title")
             .allowElements("a")

以上为修改。

/package/apps/UnifiedEmail/src/com/android/mail/providers/Message.java

bodyhtml = cursor.getString(UIProvider.MESSAGE_BODY_HTML_COLUME);


"Email正文webview加载方式

使用android.webkit.WebView控件

在xml布局文件中定义

<WebView

  android:id=”@+id/webkit01”

  android:layout:width=”fill_parent”

  android:layout:height=”fill_parent”

  android:layout:weight=”1” />

在程序中使用WebView

mWebView = (WebView) findViewById(R.id.webview01);

mWebView.loadUrl(“http://www.google.com”);

通过WebSettings来设置WebView的属性和状态

WebSettings webSettings = mWebview.getSettings();

当WebView销毁后,再使用WebSettings会抛出IllegalStateException异常。

WebSettings常用方法

1. setAllowFileAccess, 启用/禁止WebKit访问文件数据

2. setBlockNetworkImage, 是否显示网络图像

3. setBuiltInZoomControls, 是否支持缩放

4. setCacheMode, 设置缓存模式

5. setDefaultFontSize, 设置默认字体大小

6. setDefaultTextEncodingName, 设置默认的解码方式

7. setFixedFontFamily, 设置固定使用的字体

8. setJavaScriptEnabled, 是否支持JavaScript

9. setLayoutAlgorithm, 设置布局方式

10. setLightTouchEnabled, 

11. setSupportZoom, 是否支持变焦

使用WebViewClient

WebViewClient用来处理各种通知、请求等事件,WebView调用setWebViewClient()来指定一个WebViewClient对象。

WebViewClient常用方法

1. doUpdateVisitedHistory, 更新历史记录

2. onFormResubmission, 重新请求网页数据

3. onLoadResource, 加载资源

4. onPageFinished, 网页加载完毕

5. onPageStarted, 网页开始加载

6. onReceivedError, 报告错误信息

7. onScaleChanged, 发生Scale改变

8. shouldOverrideUrlLoading, 控制新的连接在当前WebView中打开

使用WebChromeClient

WebChromeClient用来处理JavaScript对话框、网站图标、网站title、加载进度等。

WebChromeClient常用方法

1. onCloseWindow,

2. onCreateWindow,

3. onJsAlert,

4. onJsConfirm,

5. onJsPrompt,

6. onProgressChanged,

7. onReceivedIcon,

8. onReceivedTitle,

9. onRequestFocus

示例:实现简单浏览网页的功能

// 点击返回键返回到前一个页面

if((keyCode==KeyEvent.KEYCODE_BACK) && (mWebView.canGoBack())) 

  mWebView.goBack();

// 学习如何处理JavaScript常用对话框

WetSettings webSettings = mWebView.getSettings();

webSettings.setJavaScriptEnabled(true);

webSettings.setAllowFileAccess(true);

webSettings.setBuiltInZoomControls(true);

// 设置WebViewClient

mWebView.setWebViewClient(new WebViewClient(){

  public boolean shouldOverrideUrlLoading(WebView view, String url) {

    view.loadUrl(url);

    return true;

  }

  // onPageFinished

  public void onPageFinished(WebView view, String url) {

    super.onPageFinished(view, url);

  }

  // onPageStarted

  public void onPageStarted(WebView view, String url, Bitmap favicon) {

    super.onPageStarted(view, url, favicon);

  }

});

// 设置WebChromeClient

mWebView.setWebChromeClient(new WebChromeClient(){

// 处理JavaScript中的alert

public boolean onJsAlert(WebView view, String url, String msg, fianl JsResult result) {

  // 弹出对话框 builder.show()

  // 确定

  result.confirm();

  return true;

}

// 处理JavaScript中的confirm

public boolean onJsConfirm(WebView view, String url, String msg, fianl JsResult result) {

  // 弹出对话框 builder.show()

  // 确定

  result.confirm();

  // 否定

  result.cancel();

  return true;

}

// 处理JavaScript中的prompt

public boolean onJsPrompt(WebView view, String url, String msg, String defaultValue, fianl JsPromptResult result) {

  // 弹出对话框 builder.show()

  // 确定

  result.confirm(value);

  // 否定/取消

  result.cancel();

  return true;

}

// 处理网页加载进度条

public void onProgressChanged(WebView view, int newProgress) {

  getWindow().setFeatureInt(Window.FEATURE_PROGRESS, newProgress*100);

  super.onProgressChanged(view, newProgress);

}

// 得到网页的标题,设置app的标题title。

public void onReceivedTitle(WebView view, String title) {

  setTitle(title);

  super.onReceivedTitle(view, title);

}

});

// 连接按钮点击事件处理程序

String url = mUrlBox.getText().toString();

if(URLUtil.isNetworkUrl(url)){

  mWebView.loadUrl(url);

// 点击返回按键

public boolean onKeyDown(int keyCode, KeyEvent event) {

  if(keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {

    mWebView.goBack();

    return true;

  }

  return super.onKeyDown(keyCode, event);

}

// 示例代码:弹出对话框

final View dialogView = mInflater.inflate(R.layout.xxx, null);

Builder builder = new Builder(mContext);

builder.setTitle(“xxx”);

builder.setView(dialogView);

// 肯定

builder.setPositiveButton(android.R.string.ok, new AlertDialog.OnclickListener(){

  public void onClick(DialogInterface dialog, int which) {

  

  }

});

// 否定

builder.setNegativeButton(android.R.string.cancel, new AlertDialog.OnclickListener(){

  public void onClick(DialogInterface dialog, int which) {

  

  }

});

// 取消

builder.setOnCancelListener(new AlertDialog.OnCancelListener(){

  public void onClick(DialogInterface dialog, int which) {

  

  }

});

// 不允许取消

builder.setCancelable(false);

builder.create();

builder.show();

return true;



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值