Show IMEI/MEID in hex, dec, and barcode when appropriate

Bug: 69063060,71706655
Test: SpecialCharSequenceMgrTest
PiperOrigin-RevId: 185910798
Change-Id: I60a614d5357809eb982991caa4765d08a53fe936
diff --git a/Android.mk b/Android.mk
index c69a090..5957458 100644
--- a/Android.mk
+++ b/Android.mk
@@ -111,6 +111,7 @@
 	dialer-guava-target \
 	dialer-glide-target \
 	dialer-glide-annotation-target \
+	dialer-zxing-target \
 	jsr305 \
 	libbackup \
 	libphonenumber \
@@ -408,3 +409,14 @@
 include $(BUILD_PREBUILT)
 
 include $(CLEAR_VARS)
+
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_MODULE := dialer-zxing-target
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := ../../../external/zxing/core/core.jar
+LOCAL_UNINSTALLABLE_MODULE := true
+
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+
diff --git a/java/com/android/dialer/dialpadview/SpecialCharSequenceMgr.java b/java/com/android/dialer/dialpadview/SpecialCharSequenceMgr.java
index 2b7209e..c349d22 100644
--- a/java/com/android/dialer/dialpadview/SpecialCharSequenceMgr.java
+++ b/java/com/android/dialer/dialpadview/SpecialCharSequenceMgr.java
@@ -17,6 +17,7 @@
 package com.android.dialer.dialpadview;
 
 import android.Manifest;
+import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.DialogFragment;
@@ -28,6 +29,9 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Color;
 import android.net.Uri;
 import android.provider.Settings;
 import android.support.annotation.Nullable;
@@ -37,8 +41,15 @@
 import android.telephony.PhoneNumberUtils;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 import android.view.WindowManager;
 import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.TextView;
 import android.widget.Toast;
 import com.android.common.io.MoreCloseables;
 import com.android.contacts.common.database.NoNullCursorAsyncQueryHandler;
@@ -51,8 +62,14 @@
 import com.android.dialer.oem.MotorolaUtils;
 import com.android.dialer.telecom.TelecomUtil;
 import com.android.dialer.util.PermissionsUtil;
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
 
 /**
  * Helper class to listen for some magic character sequences that are handled specially by the
@@ -66,12 +83,9 @@
  * shared library?)
  */
 public class SpecialCharSequenceMgr {
-
-  private static final String TAG = "SpecialCharSequenceMgr";
-
   private static final String TAG_SELECT_ACCT_FRAGMENT = "tag_select_acct_fragment";
 
-  private static final String MMI_IMEI_DISPLAY = "*#06#";
+  @VisibleForTesting static final String MMI_IMEI_DISPLAY = "*#06#";
   private static final String MMI_REGULATORY_INFO_DISPLAY = "*#07#";
   /** ***** This code is used to handle SIM Contact queries ***** */
   private static final String ADN_PHONE_NUMBER_COLUMN_NAME = "number";
@@ -306,6 +320,7 @@
 
   // TODO: Use TelephonyCapabilities.getDeviceIdLabel() to get the device id label instead of a
   // hard-coded string.
+  @SuppressLint("HardwareIds")
   static boolean handleDeviceIdDisplay(Context context, String input) {
     if (!PermissionsUtil.hasPermission(context, Manifest.permission.READ_PHONE_STATE)) {
       return false;
@@ -319,29 +334,143 @@
               ? R.string.imei
               : R.string.meid;
 
-      List<String> deviceIds = new ArrayList<String>();
+      View customView = LayoutInflater.from(context).inflate(R.layout.dialog_deviceids, null);
+      ViewGroup holder = customView.findViewById(R.id.deviceids_holder);
+
       if (TelephonyManagerCompat.getPhoneCount(telephonyManager) > 1) {
         for (int slot = 0; slot < telephonyManager.getPhoneCount(); slot++) {
           String deviceId = telephonyManager.getDeviceId(slot);
           if (!TextUtils.isEmpty(deviceId)) {
-            deviceIds.add(deviceId);
+            addDeviceIdRow(holder, deviceId, /* showBarcode */ false);
           }
         }
       } else {
-        deviceIds.add(telephonyManager.getDeviceId());
+        addDeviceIdRow(holder, telephonyManager.getDeviceId(), /* showBarcode */ true);
       }
 
       new AlertDialog.Builder(context)
           .setTitle(labelResId)
-          .setItems(deviceIds.toArray(new String[deviceIds.size()]), null)
+          .setView(customView)
           .setPositiveButton(android.R.string.ok, null)
           .setCancelable(false)
-          .show();
+          .show()
+          .getWindow()
+          .setLayout(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
       return true;
     }
     return false;
   }
 
+  private static void addDeviceIdRow(ViewGroup holder, String deviceId, boolean showBarcode) {
+    if (TextUtils.isEmpty(deviceId)) {
+      return;
+    }
+
+    ViewGroup row =
+        (ViewGroup)
+            LayoutInflater.from(holder.getContext()).inflate(R.layout.row_deviceid, holder, false);
+    holder.addView(row);
+
+    // Remove the check digit, if exists. This digit is a checksum of the ID.
+    // See https://en.wikipedia.org/wiki/International_Mobile_Equipment_Identity
+    // and https://en.wikipedia.org/wiki/Mobile_equipment_identifier
+    String hex = deviceId.length() == 15 ? deviceId.substring(0, 14) : deviceId;
+
+    // If this is the valid length IMEI or MEID (14 digits), show it in all formats, otherwise fall
+    // back to just showing the raw hex
+    if (hex.length() == 14) {
+      ((TextView) row.findViewById(R.id.deviceid_hex)).setText(hex);
+      ((TextView) row.findViewById(R.id.deviceid_dec)).setText(getDecimalFromHex(hex));
+      row.findViewById(R.id.deviceid_dec_label).setVisibility(View.VISIBLE);
+    } else {
+      ((TextView) row.findViewById(R.id.deviceid_hex)).setText(deviceId);
+    }
+
+    final ImageView barcode = row.findViewById(R.id.deviceid_barcode);
+    if (showBarcode) {
+      // Wait until the layout pass has completed so we the barcode is measured before drawing. We
+      // do this by adding a layout listener and setting the bitmap after getting the callback.
+      barcode
+          .getViewTreeObserver()
+          .addOnGlobalLayoutListener(
+              new OnGlobalLayoutListener() {
+                @Override
+                public void onGlobalLayout() {
+                  barcode.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                  Bitmap barcodeBitmap =
+                      generateBarcode(hex, barcode.getWidth(), barcode.getHeight());
+                  if (barcodeBitmap != null) {
+                    barcode.setImageBitmap(barcodeBitmap);
+                  }
+                }
+              });
+    } else {
+      barcode.setVisibility(View.GONE);
+    }
+  }
+
+  private static String getDecimalFromHex(String hex) {
+    final String part1 = hex.substring(0, 8);
+    final String part2 = hex.substring(8);
+
+    long dec1;
+    try {
+      dec1 = Long.parseLong(part1, 16);
+    } catch (NumberFormatException e) {
+      LogUtil.e("SpecialCharSequenceMgr.getDecimalFromHex", "unable to parse hex", e);
+      return "";
+    }
+
+    final String manufacturerCode = String.format(Locale.US, "%010d", dec1);
+
+    long dec2;
+    try {
+      dec2 = Long.parseLong(part2, 16);
+    } catch (NumberFormatException e) {
+      LogUtil.e("SpecialCharSequenceMgr.getDecimalFromHex", "unable to parse hex", e);
+      return "";
+    }
+
+    final String serialNum = String.format(Locale.US, "%08d", dec2);
+
+    StringBuilder builder = new StringBuilder(22);
+    builder
+        .append(manufacturerCode, 0, 5)
+        .append(' ')
+        .append(manufacturerCode, 5, manufacturerCode.length())
+        .append(' ')
+        .append(serialNum, 0, 4)
+        .append(' ')
+        .append(serialNum, 4, serialNum.length());
+    return builder.toString();
+  }
+
+  /**
+   * This method generates a 2d barcode using the zxing library. Each pixel of the bitmap is either
+   * black or white painted vertically. We determine which color using the BitMatrix.get(x, y)
+   * method.
+   */
+  private static Bitmap generateBarcode(String hex, int width, int height) {
+    MultiFormatWriter writer = new MultiFormatWriter();
+    String data = Uri.encode(hex);
+
+    try {
+      BitMatrix bitMatrix = writer.encode(data, BarcodeFormat.CODE_128, width, 1);
+      Bitmap bitmap = Bitmap.createBitmap(bitMatrix.getWidth(), height, Config.RGB_565);
+
+      for (int i = 0; i < bitMatrix.getWidth(); i++) {
+        // Paint columns of width 1
+        int[] column = new int[height];
+        Arrays.fill(column, bitMatrix.get(i, 0) ? Color.BLACK : Color.WHITE);
+        bitmap.setPixels(column, 0, 1, i, 0, 1, height);
+      }
+      return bitmap;
+    } catch (WriterException e) {
+      LogUtil.e("SpecialCharSequenceMgr.generateBarcode", "error generating barcode", e);
+    }
+    return null;
+  }
+
   private static boolean handleRegulatoryInfoDisplay(Context context, String input) {
     if (input.equals(MMI_REGULATORY_INFO_DISPLAY)) {
       LogUtil.i(
diff --git a/java/com/android/dialer/dialpadview/res/layout/dialog_deviceids.xml b/java/com/android/dialer/dialpadview/res/layout/dialog_deviceids.xml
new file mode 100644
index 0000000..89e256b
--- /dev/null
+++ b/java/com/android/dialer/dialpadview/res/layout/dialog_deviceids.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+  <LinearLayout
+      android:id="@+id/deviceids_holder"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:paddingStart="?dialogPreferredPadding"
+      android:paddingEnd="?dialogPreferredPadding"
+      android:orientation="vertical"/>
+</ScrollView>
diff --git a/java/com/android/dialer/dialpadview/res/layout/row_deviceid.xml b/java/com/android/dialer/dialpadview/res/layout/row_deviceid.xml
new file mode 100644
index 0000000..53f3e5d
--- /dev/null
+++ b/java/com/android/dialer/dialpadview/res/layout/row_deviceid.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginTop="?dialogPreferredPadding"
+    android:orientation="vertical">
+  <TextView
+      style="@style/DeviceIdBody"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="@string/hex"/>
+  <TextView
+      android:id="@+id/deviceid_hex"
+      style="@style/DeviceIdBody"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"/>
+  <TextView
+      android:id="@+id/deviceid_dec_label"
+      style="@style/DeviceIdBody"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:text="@string/dec"
+      android:visibility="gone"/>
+  <TextView
+      android:id="@+id/deviceid_dec"
+      style="@style/DeviceIdBody"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"/>
+  <ImageView
+      android:id="@+id/deviceid_barcode"
+      android:layout_width="match_parent"
+      android:layout_height="@dimen/barcode_height"
+      android:layout_margin="24dp"
+      android:layout_gravity="center_horizontal"/>
+</LinearLayout>
diff --git a/java/com/android/dialer/dialpadview/res/values/dimens.xml b/java/com/android/dialer/dialpadview/res/values/dimens.xml
index 4b386ee..6892fcf 100644
--- a/java/com/android/dialer/dialpadview/res/values/dimens.xml
+++ b/java/com/android/dialer/dialpadview/res/values/dimens.xml
@@ -43,4 +43,6 @@
 
   <!-- Length of dialpad's shadows in dialer. -->
   <dimen name="shadow_length">10dp</dimen>
+
+  <dimen name="barcode_height">100dp</dimen>
 </resources>
diff --git a/java/com/android/dialer/dialpadview/res/values/strings.xml b/java/com/android/dialer/dialpadview/res/values/strings.xml
index 094ca6b..51367b6 100644
--- a/java/com/android/dialer/dialpadview/res/values/strings.xml
+++ b/java/com/android/dialer/dialpadview/res/values/strings.xml
@@ -77,6 +77,12 @@
   <!-- The title of a dialog that displays the MEID of the CDMA phone -->
   <string name="meid">MEID</string>
 
+  <!-- The label for the hex encoding of the device ID -->
+  <string name="hex" translatable="false">HEX:</string>
+
+  <!-- The label for the decimal encoding of the device ID -->
+  <string name="dec" translatable="false">DEC:</string>
+
   <!-- Dialog text displayed when loading a phone number from the SIM card for speed dial -->
   <string name="simContacts_emptyLoading">Loading from SIM card\u2026</string>
 
diff --git a/java/com/android/dialer/dialpadview/res/values/styles.xml b/java/com/android/dialer/dialpadview/res/values/styles.xml
index d69e6a8..75a54a3 100644
--- a/java/com/android/dialer/dialpadview/res/values/styles.xml
+++ b/java/com/android/dialer/dialpadview/res/values/styles.xml
@@ -116,4 +116,9 @@
     <item name="dialpad_voicemail_tint">?attr/dialpad_text_color_secondary</item>
     <item name="dialpad_background">#00000000</item>
   </style>
+
+  <style name="DeviceIdBody">
+    <item name="android:textColor">?android:textColorPrimary</item>
+    <item name="android:textSize">16sp</item>
+  </style>
 </resources>