1e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler/* 2e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler * Copyright (C) 2015 The Android Open Source Project 3e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler * 4e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler * Licensed under the Apache License, Version 2.0 (the "License"); 5e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler * you may not use this file except in compliance with the License. 6e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler * You may obtain a copy of the License at 7e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler * 8e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler * http://www.apache.org/licenses/LICENSE-2.0 9e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler * 10e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler * Unless required by applicable law or agreed to in writing, software 11e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler * distributed under the License is distributed on an "AS IS" BASIS, 12e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler * See the License for the specific language governing permissions and 14e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler * limitations under the License 15e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler */ 16e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler 17e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantlerpackage com.android.settingslib; 18e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler 19e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantlerimport android.content.Context; 20e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantlerimport android.content.Intent; 21e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantlerimport android.content.pm.ApplicationInfo; 22e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantlerimport android.content.pm.PackageManager; 23e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantlerimport android.content.pm.ResolveInfo; 24e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantlerimport android.os.Build; 2575ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhangimport android.telephony.PhoneNumberUtils; 2675ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhangimport android.telephony.SubscriptionInfo; 2775ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhangimport android.telephony.TelephonyManager; 28e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantlerimport android.text.TextUtils; 29e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantlerimport android.text.format.DateFormat; 30e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantlerimport android.util.Log; 31e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler 32e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantlerimport java.io.BufferedReader; 33e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantlerimport java.io.FileReader; 34e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantlerimport java.io.IOException; 35e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantlerimport java.text.ParseException; 36e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantlerimport java.text.SimpleDateFormat; 37e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantlerimport java.util.Date; 38e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantlerimport java.util.List; 39e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantlerimport java.util.Locale; 40e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantlerimport java.util.regex.Matcher; 41e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantlerimport java.util.regex.Pattern; 42e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler 4375ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhangimport static android.content.Context.TELEPHONY_SERVICE; 4475ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang 45e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantlerpublic class DeviceInfoUtils { 46e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler private static final String TAG = "DeviceInfoUtils"; 47e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler 48e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler private static final String FILENAME_PROC_VERSION = "/proc/version"; 49e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler private static final String FILENAME_MSV = "/sys/board_properties/soc/msv"; 50e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler 51e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler /** 52e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler * Reads a line from the specified file. 53e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler * @param filename the file to read from 54e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler * @return the first line, if any. 55e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler * @throws IOException if the file couldn't be read 56e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler */ 57e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler private static String readLine(String filename) throws IOException { 58e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler BufferedReader reader = new BufferedReader(new FileReader(filename), 256); 59e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler try { 60e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler return reader.readLine(); 61e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } finally { 62e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler reader.close(); 63e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } 64e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } 65e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler 66e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler public static String getFormattedKernelVersion() { 67e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler try { 68e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler return formatKernelVersion(readLine(FILENAME_PROC_VERSION)); 69e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } catch (IOException e) { 70e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler Log.e(TAG, "IO Exception when getting kernel version for Device Info screen", 71e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler e); 72e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler 73e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler return "Unavailable"; 74e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } 75e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } 76e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler 77e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler public static String formatKernelVersion(String rawKernelVersion) { 78e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler // Example (see tests for more): 79e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler // Linux version 3.0.31-g6fb96c9 (android-build@xxx.xxx.xxx.xxx.com) \ 80e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler // (gcc version 4.6.x-xxx 20120106 (prerelease) (GCC) ) #1 SMP PREEMPT \ 81e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler // Thu Jun 28 11:02:39 PDT 2012 82e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler 83e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler final String PROC_VERSION_REGEX = 84e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler "Linux version (\\S+) " + /* group 1: "3.0.31-g6fb96c9" */ 857091e2fdc016932e497223f09eab1287aa8bbd0bNick Desaulniers "\\((\\S+)\\)" + /* group 2: "x@y.com" (kernel builder) */ 867091e2fdc016932e497223f09eab1287aa8bbd0bNick Desaulniers ".*(#\\d+)" + /* group 3: "#1" */ 877091e2fdc016932e497223f09eab1287aa8bbd0bNick Desaulniers /* group 4: "Thu Jun 28 11:02:39 PDT 2012" */ 887091e2fdc016932e497223f09eab1287aa8bbd0bNick Desaulniers ".*((?:Sun|Mon|Tue|Wed|Thu|Fri|Sat).+)"; 89e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler 90e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler Matcher m = Pattern.compile(PROC_VERSION_REGEX).matcher(rawKernelVersion); 91e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler if (!m.matches()) { 92e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler Log.e(TAG, "Regex did not match on /proc/version: " + rawKernelVersion); 93e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler return "Unavailable"; 94e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } else if (m.groupCount() < 4) { 95e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler Log.e(TAG, "Regex match on /proc/version only returned " + m.groupCount() 96e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler + " groups"); 97e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler return "Unavailable"; 98e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } 99e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler return m.group(1) + "\n" + // 3.0.31-g6fb96c9 100e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler m.group(2) + " " + m.group(3) + "\n" + // x@y.com #1 101e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler m.group(4); // Thu Jun 28 11:02:39 PDT 2012 102e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } 103e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler 104e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler /** 105e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler * Returns " (ENGINEERING)" if the msv file has a zero value, else returns "". 106e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler * @return a string to append to the model number description. 107e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler */ 108e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler public static String getMsvSuffix() { 109e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler // Production devices should have a non-zero value. If we can't read it, assume it's a 110e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler // production device so that we don't accidentally show that it's an ENGINEERING device. 111e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler try { 112e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler String msv = readLine(FILENAME_MSV); 113e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler // Parse as a hex number. If it evaluates to a zero, then it's an engineering build. 114e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler if (Long.parseLong(msv, 16) == 0) { 115e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler return " (ENGINEERING)"; 116e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } 117e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } catch (IOException|NumberFormatException e) { 118e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler // Fail quietly, as the file may not exist on some devices, or may be unreadable 119e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } 120e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler return ""; 121e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } 122e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler 123e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler public static String getFeedbackReporterPackage(Context context) { 124e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler final String feedbackReporter = 125e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler context.getResources().getString(R.string.oem_preferred_feedback_reporter); 126e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler if (TextUtils.isEmpty(feedbackReporter)) { 127e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler // Reporter not configured. Return. 128e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler return feedbackReporter; 129e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } 130e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler // Additional checks to ensure the reporter is on system image, and reporter is 131e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler // configured to listen to the intent. Otherwise, dont show the "send feedback" option. 132e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler final Intent intent = new Intent(Intent.ACTION_BUG_REPORT); 133e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler 134e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler PackageManager pm = context.getPackageManager(); 135e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler List<ResolveInfo> resolvedPackages = 136e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler pm.queryIntentActivities(intent, PackageManager.GET_RESOLVED_FILTER); 137e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler for (ResolveInfo info : resolvedPackages) { 138e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler if (info.activityInfo != null) { 139e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler if (!TextUtils.isEmpty(info.activityInfo.packageName)) { 140e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler try { 141e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler ApplicationInfo ai = 142e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler pm.getApplicationInfo(info.activityInfo.packageName, 0); 143e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 144e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler // Package is on the system image 145e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler if (TextUtils.equals( 146e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler info.activityInfo.packageName, feedbackReporter)) { 147e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler return feedbackReporter; 148e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } 149e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } 150e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } catch (PackageManager.NameNotFoundException e) { 151e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler // No need to do anything here. 152e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } 153e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } 154e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } 155e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } 156e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler return null; 157e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } 158e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler 159e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler public static String getSecurityPatch() { 160e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler String patch = Build.VERSION.SECURITY_PATCH; 161e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler if (!"".equals(patch)) { 162e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler try { 163e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler SimpleDateFormat template = new SimpleDateFormat("yyyy-MM-dd"); 164e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler Date patchDate = template.parse(patch); 165e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler String format = DateFormat.getBestDateTimePattern(Locale.getDefault(), "dMMMMyyyy"); 166e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler patch = DateFormat.format(format, patchDate).toString(); 167e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } catch (ParseException e) { 168e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler // broken parse; fall through and use the raw string 169e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } 170e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler return patch; 171e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } else { 172e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler return null; 173e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } 174e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler } 175e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler 17675ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang public static String getFormattedPhoneNumber(Context context, SubscriptionInfo subscriptionInfo) { 17775ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang String formattedNumber = null; 17875ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang if (subscriptionInfo != null) { 17975ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang final TelephonyManager telephonyManager = 18075ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE); 18175ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang final String rawNumber = 18275ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang telephonyManager.getLine1Number(subscriptionInfo.getSubscriptionId()); 18375ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang if (!TextUtils.isEmpty(rawNumber)) { 18475ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang formattedNumber = PhoneNumberUtils.formatNumber(rawNumber); 18575ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang } 18675ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang 18775ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang } 18875ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang return formattedNumber; 18975ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang } 19075ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang 19175ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang public static String getFormattedPhoneNumbers(Context context, 19275ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang List<SubscriptionInfo> subscriptionInfo) { 19375ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang StringBuilder sb = new StringBuilder(); 19475ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang if (subscriptionInfo != null) { 19575ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang final TelephonyManager telephonyManager = 19675ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE); 19775ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang final int count = subscriptionInfo.size(); 19875ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang for (int i = 0; i < count; i++) { 19975ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang final String rawNumber = telephonyManager.getLine1Number( 20075ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang subscriptionInfo.get(i).getSubscriptionId()); 20175ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang if (!TextUtils.isEmpty(rawNumber)) { 20275ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang sb.append(PhoneNumberUtils.formatNumber(rawNumber)); 20375ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang if (i < count - 1) { 20475ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang sb.append("\n"); 20575ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang } 20675ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang } 20775ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang } 20875ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang } 20975ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang return sb.toString(); 21075ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang } 21175ee8d18f1fa986bcdd896d1bbc610b5173d7222Fan Zhang 212e92cf6d7396a044c578ce2338890bcbb5e98ed75Tony Mantler} 213