音量键自定义

什么是音量键自定义

2.1.0版本后的SUNMIUI加入了音量键自定义功能。用户可以在系统设置中设置单按上、下音量键快捷操作指定应用的特定功能,设置流程如下所示。

1

开发者怎么使用

目前可以支持的快捷操作包括:打开应用,打开扫码页,打开显示二维码页面。

 

其中设置打开应用不需要应用开发者配合修改代码即可实现,扫码和显示二维码需要应用本身有这些功能,然后开发者按照商米的规则在AndroidManifest.xml中该功能页配置中添加配置信息,用户才能在自定义音量键的时候指定按音量键操作该应用的这些功能。

所有的配置都是在项目的AndroidManifest.xml中添加,下面以支持快速启动扫码为例,介绍需要在自己App种添加的配置信息。

1.在扫码页的Activity配置中设置启动模式为singleInstance。

2.在intent-filter配置中添加catetory,固定写法android.intent.category.DEFAULT。

3.在intent-filter中添加action,action的规则为:开发者的应用包名+商米规定的业务功能后缀。如您的应用包名com.example.test,业务功能名为scan,则actioname为com.example.test.scan,如下图所示。

10175015530457499

显示二维码的配置方式类似,只是action配置项不同,以下列出所有action动作的列表。

3

注:以上为目前支持的操作,更多的操作动作在后续添加。

商米扫码

为什么要使用商米封装的扫码SDK

商米提供了适配自己设备的扫码SDK,相对于目前使用的开源方案,商米的扫码SDK有以下5个优势

  1. 识别率高,经过大量模拟真实场景的测试,商米扫码SDK相对普遍使用的基于ZXing开源项目的扫码方案在扫码识别率上平均提高了74%。
  2. 比ZXing方案在一维码扫码速度上快了100%以上。
  3. 使用方式更简单,5行代码就能在自己的项目中添加扫码功能。
  4. 支持扫描多达15种码,后续还将添加更多的码种。
  5. 与商米的设备完美适配,软硬件结合可以保证功能的高效稳定。

怎么使用商米的扫码SDK

开发者有两种方式使用商米的扫码SDK

  1. 开发者的应用调用SUNMIUI系统集成的扫码模块完成扫码,获取返回值,该方法简单易用。
  2. 自己写相机界面,调用商米的封装的扫码SDK完成图片的解析,该方式相对复杂,但提供了更高的自由度。

第一种使用方式:

为了降低开发难度,商米在最新的SUNMI OS(V1固件版本187,M1固件版本37)系统中内置了一个扫码的模块,开发者在项目需要调用扫码的地方通过startActivityForResult()调用商米的扫码模块,然后在onActivityResult()方法中接受扫码结果返回值。

/**

	* 外部应用在自己的业务代码需要启动扫码的地方使用下面的方式创建Intent,

	* 然后使用startActivityForResult()调用起商米的扫码模块;

	*/

	Intent intent = new Intent("com.summi.scan");

	intent.setPackage("com.sunmi.sunmiqrcodescanner");

	        

	/**

	* 使用该方式也可以调用扫码模块

	*Intent intent = new Intent("com.summi.scan");

	*intent.setClassName("com.sunmi.sunmiqrcodescanner", 

	"com.sunmi.sunmiqrcodescanner.activity.ScanActivity");

	*/

	/**

	//扫码模块有一些功能选项,开发者可以通过传递参数控制这些参数,

	//所有参数都有一个默认值,开发者只要在需要的时候添加这些配置就可以。

	intent.putExtra("CURRENT_PPI", 0X0003);//当前分辨率 

	//M1和V1的最佳是800*480,PPI_1920_1080 = 0X0001;PPI_1280_720 = 

	//0X0002;PPI_BEST = 0X0003;

	intent.putExtra("PLAY_SOUND", true);// 扫描完成声音提示  默认true

	intent.putExtra("PLAY_VIBRATE", false);

	//扫描完成震动,默认false,目前M1硬件支持震动可用该配置,V1不支持

	intent.putExtra("IDENTIFY_INVERSE_QR_CODE", true);// 识别反色二维码,默认true

	intent.putExtra("IDENTIFY_MORE_CODE", false);// 识别画面中多个二维码,默认false        

	intent.putExtra("IS_SHOW_SETTING", true);// 是否显示右上角设置按钮,默认true

	intent.putExtra("IS_SHOW_ALBUM", true);// 是否显示从相册选择图片按钮,默认true

	*/
	startActivityForResult(intent, START_SCAN);           

在onActivityResult方法中接收返回的扫码结果参数,参考如下代码: @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == 1 && data != null) { Bundle bundle = data.getExtras(); ArrayList> result = (ArrayList>) bundle .getSerializable("data"); Iterator> it = result.iterator(); while (it.hasNext()) { HashMap hashMap = it.next(); Log.i("sunmi", hashMap.get("TYPE"));//这个是扫码的类型 Log.i("sunmi", hashMap.get("VALUE"));//这个是扫码的结果 } } super.onActivityResult(requestCode, resultCode, data); }

 @Override

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

super.onActivityResult(requestCode, resultCode, data);

if (requestCode == 1 && data != null) {

Bundle bundle = data.getExtras();

ArrayList result = (ArrayList<>) bundle .getSerializable(“data”);

Iterator<> it = result.iterator();

while (it.hasNext()) {

HashMap hashMap = it.next();

Log.i(“sunmi”, hashMap.get(“TYPE”));//这个是扫码的类型

Log.i(“sunmi”, hashMap.get(“VALUE”));//这个是扫码的结果

}

}

}

第二种方式:

1. 在项目的libs目录中按以下层级添加libiconv.so,libscaninit.so,libsunmiscan.so和sunmiscan.jar四个库文件。

project

2. 在处理业务的代码中引入头文件和解码库,可以参照DEMO。

        import com.sunmi.scan.Config;

	import com.sunmi.scan.Image; 

	import com.sunmi.scan.ImageScanner; 

	import com.sunmi.scan.Symbol;

	import com.sunmi.scan.SymbolSet; 

        private ImageScanner scanner;//声明扫描器 

	scanner = new ImageScanner();//创建扫描器

	scanner.setConfig(0, Config.X_DENSITY, 2);//行扫描间隔

	scanner.setConfig(0, Config.Y_DENSITY, 2);//列扫描间隔

	scanner.setConfig(0, Config.ENABLE_MULTILESYMS, 0);

	//是否开启同一幅图一次解多个条码,0表示只解一个,1为多个

	scanner.setConfig(0, Config.ENABLE_INVERSE, 0);//是否解反色的条码  

        scanner.setConfig( Symbol.QRCODE,Config.ENABLE, 1);//允许识读QR码,默认1:允许

        scanner.setConfig( Symbol.PDF417,Config.ENABLE, 1);//允许识读PDF417码,默认0:禁止

        scanner.setConfig(Symbol.DataMatrix, Config.ENABLE, 1);//允许识读DataMatrix码,默认0:禁止

        scanner.setConfig(Symbol.AZTEC, Config.ENABLE, 1);//允许识读AZTEC码,默认0:禁止

4.传入图像数据和解码,以下的代码可以写在PreviewCallback.onPreviewFrame(byte[] data, Camera camera)方法中。

  /**
    *创建解码图像,width, height 分别为摄像头预览分辨率的宽度和高度,一般来说,分辨率越高图
    *像越清晰,但解码速度越慢。由于解码算法需要处理的是原始灰度数据,而预览图像的默认格式为 
    *YCbCr_420_SP,需要转换格式才能处理, 参数"Y800"表示待转换的图像格式。
    */
    Image source = new Image(width, height, "Y800");

    /**
    *设置扫描区域范围,为了较好的识读较长的一维码,扫描框的宽度不应过小,由于预览的图像为横屏, 
    *注意扫描区域需要转换为竖屏对应的位置 
    */
    Rect cropRect = finder_view.getScanImageRect(size.height, size.width);
    //finder_view为DEMO中自定义的扫码区域控件。
    source.setCrop(cropRect.top,cropRect.left,cropRect.height(),cropRect.width());

    /*填充图像数据,data 为摄像头原始数据*/
    source.setData(data); 

    int result = scanner.scanImage(source); 

5.获取解码结果和条码类型。

 {
         
      SymbolSet syms = scanner.getResults();
          for (Symbol sym : syms) {   
            Log.i("sunmi", "码型:"+sym.getSymbolName());//条码类型,如“EAN-8”
            Log.i("sunmi","结果:"+sym.getResult())//获取解码结果字符串,这里就是要获取的结果
            
            }

  }       

so更新记录

版本更新内容
1.3.3 1.修正部分一维码(Code39,Code93,Interleaved25,Codabar,Code128)无法禁止识读的bug
2.jar包版本更新为1.1.3,libsunmiscan.so版本号更新为1.3.3
1.3.0 1.去掉libiconv.so,即解码库只有一个so文件libsunmiscan.so(包括32,64位平台)
2.解决内存泄漏问题
3.增加点阵打印QR码识读,开启方式scanner.setConfig(Symbol.QRCODE, Config.ENABLE_DPM,1);//允许识读点阵打印QR码,默认0:禁止
1.2.9 1.增加64位平台(arm64-v8a)解码库
2.去掉libscaninit.so,同时jar包去掉libscaninit.so库版本查询接口
3.jar包版本更新为1.1.1
4.修复若干由于增加64位解码库发现的兼容性问题
1.2.8 1.优化code39解析能力
2.针对银联扫码能力测试要求的部分修改,该版本已通过银联扫码能力测试和最佳实践测试
3. 增加ISBN10 ISBN13输出使能控制
1.2.6 1.增加解码结果字节模式输出接口
2.解决倾斜角度扫一维码偶现程序异常的问题
1.2.5 1. 增加解码结果字节模式输出接口
2.优化一维码定位算法和解码时间
1.2.4 1.解决码字符中有不可以显示字符导致的解码结果字符解析异常的问题
2.其他优化
1.2.2 1. 优化打开相册读取高分辨率的图片解码慢的问题
2.解决使用Android7.0后硬件管家的扫码成功后没有结果返回的问题,
1.2.1 1.新增支持的二维码DataMatrix和Aztec(默认关闭,需要设置开启识读)
2.支持识读缺损一个定位符的QR码的识读
3.解决因开启多码同时识读时的bug
1.1.0 1.一维码可360度识读,之前的版本只在接近水平垂直方向识读
2.QR码识别速度提升一倍
3.解决QR码混合编码(中英文等)译码出现的bug
4.解决PDF417测试出现的潜在内存异常风险(如除0,内存溢出等)
1.0.1 1.增加了二维码PDF417的解码算法(默认关闭,需要设置开启识读)
2.减少了部分一维码的误码率
3.对于比较长的一维码可以竖屏全屏读取
4.对于上下并排的多个一维码,优先读取中间的条码
5.一维码和QR码支持反色和镜像解码,PDF417支持镜像解码
6.增加了解码库版本查询功能
1.0.0基础版本, 支持识读二维码QR和10多种主流一维码

更多说明

商米扫码SDK目前支持的扫码类型包括如下:

  • 一维码: EAN-8, EAN-13, UPC-A, UPC-E, Codabar, Code39, Code93, Code128, ISBN10, ISBN13, DataBar, DataBar Expanded, Interleaved 2 of 5
  • 二维码: QR Code , PDF417,DataMatrix,AZTEC

T1副屏内置副显程序对接

 

T1 接入副屏客显程序说明

更新内容

2017-12-28 (适用版本:主屏(T1-host): V1.14.22、7寸副屏(sub7): V1.11.3、14寸副屏(sub14): V1.9.11)

  • 新增14寸副屏支持轮播视频;
  • 新增14寸副屏支持视频续播;
  • 新增7寸副屏支持幻灯片;
  • 新增7寸副屏支持单个视频;
  • 新增7寸副屏支持视频轮播;
  • 新增7寸副屏支持视频续播;
  • 新增判断副屏尺寸的方法;
  • 新增管理副屏缓存文件的方法;
  • DS_Lib库更新至1.0.16;

2018-01-15

  • 更新下载资源;

                                                                                                                                                                                                                                       

一、简介

T1双屏机器有三种组合:主机、主机+7寸副屏、主机+14寸

初始化配置

副屏。主副屏都是运行SUNMI OS定制系统,通过商米已封装好的接口实现通信。

主屏主要用来运行业务APP,例如:收银系统。副屏主要面向顾客显示结算、广告内容。开发者有两种方式实现副屏显示:

  • T1副屏系统内置了默认副屏显示APP,内置多个常用模板。开发者仅需参照文中模板的实现代码,即可实现副屏内容显示;
  • 开发自己副屏显示APP,需要自行处理数据的收发、内容显示等动作;

如果您没有强烈的自定义需求,商米建议您使用默认副屏显示APP,可以节省很多的研发成本。

本文将对如何接入内置副屏客显程序进行说明。

二、如何调试应用

由于主副屏是通过USB通信的,当主/副屏插入USB线时,主副屏的连接会断开,导致无法调试设备。

商米提供的解决方案是:将开发电脑与将要调试的T1主机处于同一个局域网络环境下,通过网络对设备进行调试。

如何调试:

1、开启USB调试,及调试权限;

    操作说明

2、通过网络对设备进行ADB调试;

    操作说明

三、如何接入副屏客显程序

(强调:Sunmi OS是基于Android6.0定制,Android6.0+ 要求部分敏感权限需要动态申请)

商米默认副屏APP提供了多个显示模板,开发者仅需要在自己的主屏业务APP实现控制副屏输出的代码,向默认副屏APP发送正确格式的数据即可实现副屏显示。

1、初始化配置

对接7寸或14寸默认副屏APP的初始化流程一样。

步骤1:

    下载DemoApp资源文件。

步骤2:

    参照DemoApp源码,直接在Android Studio的app module下的build.gradle文件中声明以下代码:

 dependencies {
    compile 'com.sunmi:DS_Lib:1.0.15'   //商米提供的lib库,包含已封装好的接口
    compile 'compile 'com.alibaba:fastjson:1.1.67.android''  //fastjson任意版本
}

步骤3:

    在清单文件AndroidMainfest.xml的节点下配置以下声明:


  ....
  "sunmi.ds.MsgReceiver">  //接收数据的广播
     
         "com.sunmi.hcservice">
         "com.sunmi.hcservice.status">
      
  

步骤4:

    在适当的位置初始化SDK代码,可以参考DSC_Demo中的实现代码。

DSKernel mDSKernel =DSKernel.newInstance();
mDSKernel.init(context, mConnCallback);    //绑定服务的回调
mDSKernel.addReceiveCallback(mReceiveCallback);  //双屏通信接收数据回调

接下来可以参考DemoApp源码,实现套用默认副屏APP内置模板的内容显示。

混淆ds_lib:

-keep interface sunmi.ds.**{ *; }

-keep class sunmi.ds.**{ *; }

除此之外,还需要混淆greendao以及fastjson,具体混淆规则以greendao和fastjson官方提供为准

2、副屏客显程序内置模板

7寸与14寸副屏支持的模板对比:

 模板7寸 14寸 
 文字 √ √
 文字+二维码 √ √
 单张图片 √ √
 幻灯片 √ √
 单个视频 √ √
 视频轮播 √ √
 清单 × √
 清单+单张图片 × √
 清单+幻灯片 × √
 清单+单个视频  × √
 清单+视频轮播 × √

2.1、两行文字

png

实现代码:

JSONObject json = new JSONObject();
json.put("title", title);//title为上面一行的标题内容
json.put("content", content);//content为下面一行的内容
String jsonStr = json.toString();
//构建DataPacket类
DataPacket packet = UPacketFactory.buildShowText(DSKernel.getDSDPackageName(), jsonStr, callback);//第一个参数是接收数据的副显应用的包名,这里参照Demo就可以,第二个参数是要显示的内容字符串,第三个参数为结果回调。

mDSKernel.sendData(packet);//调用sendData方法发送文本

2.2、二维码+文字

2

实现代码:

JSONObject json = new JSONObject();
try {
    json.put("title", "微信支付");
    json.put("content", "10.00");
} catch (JSONException e) {
    e.printStackTrace();
}

mDSKernel.sendFile(DSKernel.getDSDPackageName(), json.toString(), Environment.getExternalStorageDirectory().getPath() + "/qrcode.png", new ISendCallback() {
    @Override
    public void onSendSuccess(long l) {
        //显示图片
        try {
            JSONObject json = new JSONObject();
            json.put("dataModel", "QRCODE");
            json.put("data", "default");
            mDSKernel.sendCMD(SF.DSD_PACKNAME, json.toString(), l, null);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onSendFail(int i, String s) {      
    }

    @Override
    public void onSendProcess(long l, long l1) {
    }
});

2.3、单张图片

3

实现代码:

mDSKernel.sendFile(DSKernel.getDSDPackageName(), Environment.getExternalStorageDirectory().getPath() + "/img_01.png", new ISendCallback() {

        @Override
        public void onSendSuccess(long taskId) {
            showPicture(taskId);
        }

        @Override
        public void onSendFail(int errorId, String errorInfo) {
        }

        @Override
        public void onSendProcess(long totle, long sended) {
        }
    });
}

/**
 * 展示单张图片
 *
 * @param taskId
 */

private void showPicture(long taskId) {
    //显示图片
    try {
        JSONObject json = new JSONObject();
        json.put("dataModel", "SHOW_IMG_WELCOME");
        json.put("data", "default");
        mDSKernel.sendCMD(DSKernel.getDSDPackageName(), json.toString(), taskId, null);
    } catch (JSONException e) {
        e.printStackTrace();
    }
}

2.4、幻灯片

4

实现代码:

JSONObject json = new JSONObject();
json.put("rotation_time",5000); //幻灯片的切换时间,用毫秒计算,如果不传默认是10000毫秒
List pathList = new ArrayList<>();
pathList.add("/sdcard/img1.png");
pathList.add("/sdcard/img2.png");
...
mDSKernel.sendFiles(DSKernel.getDSDPackageName(), json.toString(), 
pathList, new ISendFilesCallback() {
    public void onAllSendSuccess(long fileId) {
        show(fileId);
    }
    public void onSendSuccess(final String s,final long l) {}
    public void onSendFaile(int errorId, String errorInfo) {}
    public void onSendFileFaile(String path, int errorId, String errorInfo){}
    public void onSendProcess(String path, long total, long sended) {}
});

private void show(long fileId) {
    String json = UPacketFactory.createJson(DataModel.IMAGES,"");
    mDSKernel.sendCMD(DSKernel.getDSDPackageName(),json,fileId,null);
}

2.5、单个视频

5

实现代码:

mDSKernel.sendFile(DSKernel.getDSDPackageName(), Environment.getExternalStorageDirectory().getPath() + "/video_01.mp4", new sunmi.ds.callback.ISendCallback() {
        @Override
        public void onSendSuccess(long l) {         
            playvideo(l);         
        }

        @Override
        public void onSendFail(int i, String s) {
            Log.d("highsixty", "发送单个文件视频文件失败 ------------>" + s);          
        }

        @Override
        public void onSendProcess(final long l, final long l1) {
        }
    });
}

private void playvideo(long taskID) {  
    String json = UPacketFactory.createJson(DataModel.VIDEO, "true"); //"true"视频续播;false视频从头播放
    mDSKernel.sendCMD(DSKernel.getDSDPackageName(), json, taskID, null);
}

2.6、视频轮播

6

实现代码:

/**
 * 发送多视频
 */

private void sendVideos() {
    //请对文件是否存在做判断
    List files = new ArrayList<>();
    files.add(Environment.getExternalStorageDirectory().getPath() + "/video_01.mp4");
    files.add(Environment.getExternalStorageDirectory().getPath() + "/video_02.mp4");
    files.add(Environment.getExternalStorageDirectory().getPath() + "/video_03.mp4");
    mDSKernel.sendFiles(DSKernel.getDSDPackageName(), "", files, new ISendFilesCallback() {

        @Override
        public void onAllSendSuccess(long fileid) {
            playvideos(fileid);
        }

        @Override
        public void onSendSuccess(String path, long taskId) {           
        }

        @Override
        public void onSendFaile(int errorId, String errorInfo) {           
        }

        @Override
        public void onSendFileFaile(String path, int errorId, String errorInfo) {            
        }

        @Override
        public void onSendProcess(String path, long totle, long sended) {
        }
    });
}

private void playvideos(long taskID) {
    Log.d(TAG, "playvideos: ------------>" + taskID);
    String json = UPacketFactory.createJson(DataModel.VIDEOS, "true"); //第二个参数true:视频续播 false:视频从头播放
    mDSKernel.sendCMD(DSKernel.getDSDPackageName(), json, taskID, null);
}

2.7、清单

7

实现代码:

{
        "title": "三米奶茶店欢迎您", 
        "head": {
            "param1": "序列号",
            "param2": "商品名",
            "param3": "单价",
            "param4": "数量",
            "param5": "小结"
        },
        "list": [
            {
              "param1": "1",
              "param2": "华夫饼",
              "param3": "10.00",
              "param4": "1",
             "param5":"10.00"
            },
            {
              "param1": "1",
              "param2": "吞拿鱼华夫饼",
              "param3": "12.00",
              "param4": "1",
             "param5":"12.00"
            }
            ... ...//这里是相同格式的数据
        ],
        "KVPList": [
            {
                "name": "收款",
                "value": "¥40.00"
            },
            {
                "name": "优惠",
                "value": "¥3.00"
            },
           {
                "name": "找零",
                "value": "¥3.00"
            },
           {
                "name": "实收",
                "value": "¥37.00"
            }
        ]
}

/**
*JSON数据格式说明:
*1:title字段为标题
*2:head字段是表头字段
*3:list是商品列表,表头和表的字段数量要一致 
*4:KVPList为结算键值对列表
*/

/**
*规则:
*当显示图文混合时:head的params赋值个数最小1个最大4个;list中每个元素的params赋值个数最*小1个最大4个;KVPList的size
*最小1个最大4个。
*/ 

/**
*receiverPackageName 接收数据的副屏显示app的包名DataType.DATA 
*DataType.DATA 
*DataModel.TEXT 
*jsonStr 显示的数据内容,具体格式参见下面的框
*callback 回调
*/
DataPacket pack = buildPack(receiverPackageName, DataType.DATA, DataModel.TEXT, jsonStr, callback);
mDSKernel.sendData(pack);

2.8、清单+单张图片

8

实现代码:

 {
        "title": "三米奶茶店欢迎您", 
        "head": {
            "param1": "序列号",
            "param2": "商品名",
            "param3": "单价",
            "param4": "数量",
            "param5": "小结"
        },
        "list": [
            {
              "param1": "1",
              "param2": "华夫饼",
              "param3": "10.00",
              "param4": "1",
             "param5":"10.00"
            },
            {
              "param1": "1",
              "param2": "吞拿鱼华夫饼",
              "param3": "12.00",
              "param4": "1",
             "param5":"12.00"
            }
            ... ...//这里是相同格式的数据
        ],
        "KVPList": [
            {
                "name": "收款",
                "value": "¥40.00"
            },
            {
                "name": "优惠",
                "value": "¥3.00"
            },
           {
                "name": "找零",
                "value": "¥3.00"
            },
           {
                "name": "实收",
                "value": "¥37.00"
            }
        ]
}

/**
*JSON数据格式说明:
*1:title字段为标题
*2:head字段是表头字段
*3:list是商品列表,表头和表的字段数量要一致 
*4:KVPList为结算键值对列表
*/

/**
*规则:
*当显示图文混合时:head的params赋值个数最小1个最大4个;list中每个元素的params赋值个数最*小1个最大4个;KVPList的size
*最小1个最大4个。
*/ 

String filePath = "xxx/img.png";//显示的图片路径
mDSKernel.sendFile(DSKernel.getDSDPackageName(), filePath, new ISendCallback() {
    public void onSendSuccess(long fileId) {
        show(fileId, jsonStr);//图片发送成功,显示文字内容
    } 
    public void onSendFail(int errorId, String errorInfo) {}
    public void onSendProcess(long total, long sended) {}
});

void show(long fileId, String jsonStr){
    jsonStr = UPacketFactory.createJson(DataModel.SHOW_IMG_LIST, jsonStr);
    //第一个参数DataModel.SHOW_IMG_LIST为显示布局模式,jsonStr为要显示的内容字符
    mDSKernel.sendCMD(DSKernel.getDSDPackageName(), jsonStr, fileId,null);
}

2.9、清单+幻灯片

8

实现代码:

{
        "title": "三米奶茶店欢迎您", 
        "head": {
            "param1": "序列号",
            "param2": "商品名",
            "param3": "单价",
            "param4": "数量",
            "param5": "小结"
        },
        "list": [
            {
              "param1": "1",
              "param2": "华夫饼",
              "param3": "10.00",
              "param4": "1",
             "param5":"10.00"
            },
            {
              "param1": "1",
              "param2": "吞拿鱼华夫饼",
              "param3": "12.00",
              "param4": "1",
             "param5":"12.00"
            }
            ... ...//这里是相同格式的数据
        ],
        "KVPList": [
            {
                "name": "收款",
                "value": "¥40.00"
            },
            {
                "name": "优惠",
                "value": "¥3.00"
            },
           {
                "name": "找零",
                "value": "¥3.00"
            },
           {
                "name": "实收",
                "value": "¥37.00"
            }
        ]
}

/**
*JSON数据格式说明:
*1:title字段为标题
*2:head字段是表头字段
*3:list是商品列表,表头和表的字段数量要一致 
*4:KVPList为结算键值对列表
*/

/**
*规则:
*当显示图文混合时:head的params赋值个数最小1个最大4个;list中每个元素的params赋值个数最*小1个最大4个;KVPList的size
*最小1个最大4个。
*/ 

List paths = new ArrayList<>();
paths.add(Environment.getExternalStorageDirectory().getPath() + "/sunmi1.png");
paths.add(Environment.getExternalStorageDirectory().getPath() + "/sunmi2.png");
paths.add(Environment.getExternalStorageDirectory().getPath() + "/sunmi3.png");
paths.add(Environment.getExternalStorageDirectory().getPath() + "/sunmi4.png");

mDSKernel.sendFiles(DSKernel.getDSDPackageName(), "", paths, new ISendFilesCallback() {
    @Override
    public void onAllSendSuccess(long fileId) {               
      sendImgsListCMD(fileId,json);                           
    }

    @Override
    public void onSendSuccess(String path, long taskId) {}
    @Override
    public void onSendFaile(int errorId, String errorInfo) {}
    @Override
    public void onSendFileFaile(String path, int errorId, String errorInfo) {}
    @Override
    public void onSendProcess(String path, long totle, long sended) {}
    });
    
    void sendImgsListCMD(long fileId, String jsonStr){   
      jsonStr= UPacketFactory.createJson(DataModel.SHOW_IMGS_LIST, json);
        mDSKernel.sendCMD(DSKernel.getDSDPackageName(), jsonStr, fileId,null);
    }

2.10、清单+单个视频

10

实现代码:

mDSKernel.sendFile(DSKernel.getDSDPackageName(), Environment.getExternalStorageDirectory().getPath() + "/video_01.mp4", new sunmi.ds.callback.ISendCallback() {
    @Override
    public void onSendSuccess(long l) {     
        playVideoMenu(l);      
    }

    @Override
    public void onSendFail(int i, String s) {
        Log.d("highsixty", "发送单个文件视频和清单文件失败 ------------>" + s);      
    }

    @Override
    public void onSendProcess(final long l, final long l1) {
    }
});

private void playVideoMenu(long fileId) {
    try {
        JSONObject data = new JSONObject();
        data.put("title", "商米奶茶店收银");
        JSONObject head = new JSONObject();
        head.put("param1", "序号");
        head.put("param2", "商品名");
        head.put("param3", "单价");
        data.put("head", head);
        data.put("flag", "true");    //设置为true,则视频将接着之前的进度播放。设置为false,则视频将从头播放。
        JSONArray list = new JSONArray();
        for (int i = 1; i < 11; i++) {
            JSONObject listItem = new JSONObject();
            listItem.put("param1", "" + i);
            listItem.put("param2", products.get(i - 1));
            listItem.put("param3", prices.get(i - 1));
            list.put(listItem);
        }

        data.put("list", list);
        JSONArray KVPList = new JSONArray();
        JSONObject KVPListOne = new JSONObject();
        KVPListOne.put("name", "总计 ");
        KVPListOne.put("value", "132.00");
        JSONObject KVPListTwo = new JSONObject();
        KVPListTwo.put("name", "优惠 ");
        KVPListTwo.put("value", "12.00");
        JSONObject KVPListThree = new JSONObject();
        KVPListThree.put("name", "数量 ");
        KVPListThree.put("value", "10");
        JSONObject KVPListFour = new JSONObject();
        KVPListFour.put("name", "应收 ");
        KVPListFour.put("value", "120.00");
        KVPList.put(0, KVPListOne);
        KVPList.put(1, KVPListTwo);
        KVPList.put(2, KVPListThree);
        KVPList.put(3, KVPListFour);
        data.put("KVPList", KVPList);
        Log.d("HHHH", "onClick: ---------->" + data.toString());
        String json = UPacketFactory.createJson(DataModel.SHOW_VIDEO_LIST, data.toString());
        mDSKernel.sendCMD(DSKernel.getDSDPackageName(), json, fileId, null);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

2.11、清单+视频轮播

10

实现代码:

/**
 * 左视频右清单文件
 */
private void sendMenuVideos() {
    Log.d(TAG, "sendMenuVideos: ------------->");
    List files = new ArrayList<>();
    files.add(Environment.getExternalStorageDirectory().getPath() + "/video_01.mp4");
    files.add(Environment.getExternalStorageDirectory().getPath() + "/video_02.mp4");
    files.add(Environment.getExternalStorageDirectory().getPath() + "/video_03.mp4");

    mDSKernel.sendFiles(DSKernel.getDSDPackageName(), "", files, new ISendFilesCallback() {

        @Override
        public void onAllSendSuccess(long fileid) {          
            playMenuVideos(fileid);
        }

        @Override
        public void onSendSuccess(String path, long taskId) {            
        }

        @Override
        public void onSendFaile(int errorId, String errorInfo) {           
        }

        @Override
        public void onSendFileFaile(String path, int errorId, String errorInfo) {           
        }

        @Override
        public void onSendProcess(String path, long totle, long sended) {
        }
    });
}

private void playMenuVideos(long taskID) {    
    try {
        JSONObject data = new JSONObject();
        data.put("title", "商米奶茶店收银");
        JSONObject head = new JSONObject();
        head.put("param1", "序号");
        head.put("param2", "商品名");
        head.put("param3", "单价");
        data.put("head", head);
        data.put("flag", "true"); //“true"视频续播;false 从头播放
        JSONArray list = new JSONArray();
        for (int i = 1; i < 11; i++) {
            JSONObject listItem = new JSONObject();
            listItem.put("param1", "" + i);
            listItem.put("param2", products.get(i - 1));
            listItem.put("param3", prices.get(i - 1));
            list.put(listItem);
        }
        data.put("list", list);
        JSONArray KVPList = new JSONArray();
        JSONObject KVPListOne = new JSONObject();
        KVPListOne.put("name", "总计 ");
        KVPListOne.put("value", "132.00");
        JSONObject KVPListTwo = new JSONObject();
        KVPListTwo.put("name", "优惠 ");
        KVPListTwo.put("value", "12.00");
        JSONObject KVPListThree = new JSONObject();
        KVPListThree.put("name", "数量 ");
        KVPListThree.put("value", "10");
        JSONObject KVPListFour = new JSONObject();
        KVPListFour.put("name", "应收 ");
        KVPListFour.put("value", "120.00");
        KVPList.put(0, KVPListOne);
        KVPList.put(1, KVPListTwo);
        KVPList.put(2, KVPListThree);
        KVPList.put(3, KVPListFour);
        data.put("KVPList", KVPList);
        Log.d("HHHH", "onClick: ---------->" + data.toString());
        String json = UPacketFactory.createJson(DataModel.MENUVIDEOS, data.toString());
        mDSKernel.sendCMD(DSKernel.getDSDPackageName(), json, taskID, null);
    } catch (JSONException e) {
        e.printStackTrace();
    }
}

3、判断副屏的尺寸

主屏业务应用可通过获取副屏Model值判断副屏尺寸,实现根据副屏尺寸输出相应的显示内容的业务逻辑。

3.1、从主屏获取副屏Model值

实现代码:

String subModel = Settings.Global.getString(getContentResolver(), "sunmi_sub_model")

//返回值:“t1sub14”对应14寸副屏,“t1sub7”对应7寸副屏。返回其它值表示获取失败。

3.2、从副屏获取副屏Model值

实现代码:

//从副屏获取副屏model
case R.id.btn_get_sub_model:
JSONObject jsonObject2 = new JSONObject();
try {
  jsonObject2.put("dataModel", "GET_MODEL");
  jsonObject2.put("data", "");
} catch (JSONException e) {
  e.printStackTrace();
}

DataPacket p2 = new DataPacket.Builder(DSData.DataType.CMD).recPackName(SF.SUNMI_DSD_PACKNAME).data(jsonObject2.toString())
.addCallback(new ISendCallback() {
  @Override
  public void onSendSuccess(long taskId) {
  }    
  
  @Override                         
  public void onSendFail(int errorId, String errorInfo) {           
  }     
                       
  @Override
  public void onSendProcess(long totle, long sended) {              
  }
}).build();

mDSKernel.sendQuery(p2, new QueryCallback() {                 
  @Override                 
  public void onReceiveData(final DSData data) {                      Log.d("highsixty", "onReceiveData: ------------>" + data.data); runOnUiThread(new Runnable() {                         
      @Override                         
      public void run() {                             
        Toast.makeText(MainActivity.this, "从副屏获取副屏model-->" + data.data, Toast.LENGTH_LONG).show();                         
      }
    });
  }
});             
break;
//返回值:“t1sub14”对应14寸副屏,“t1sub7”对应7寸副屏。返回其它值表示获取失败。

4、清除副屏缓存

4.1、查询副屏指定缓存目录大小

实现代码:

JSONObject jsonObject = new JSONObject();
try {
    jsonObject.put("dataModel", "GETVICECACHEFILESIZE");
    jsonObject.put("data", Environment.getExternalStorageDirectory().getAbsolutePath() + "/HCService/" + getPackageName().replace(".", "_"));
} catch (JSONException e) {
    e.printStackTrace();
}

DataPacket packet = new DataPacket.Builder(DSData.DataType.CMD).recPackName(SF.SUNMI_DSD_PACKNAME).data(jsonObject.toString())
        .addCallback(null).build();
mDSKernel.sendQuery(packet, new QueryCallback() {
    @Override
    public void onReceiveData(final DSData data) {
        Log.d("highsixty", "onReceiveData: ------------>" + data.data);
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(MainActivity.this, "副屏缓存文件大小字节数为" + data.data, Toast.LENGTH_LONG).show();
            }
        });
    }
});

4.2、清除副屏指定缓存目录

实现代码:

DataPacket packet2 = UPacketFactory.remove_folders(DSKernel.getDSDPackageName(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/HCService/" + getPackageName().replace(".", "_"), new ISendCallback() {
    @Override
    public void onSendSuccess(long taskId) {
        showToast("清除缓存文件成功");
    }

    @Override
    public void onSendFail(int errorId, String errorInfo) {
        showToast("清除缓存文件失败");
    }

    @Override
    public void onSendProcess(long totle, long sended) {
    }
});

mDSKernel.sendCMD(packet2);

4.3、查询指定缓存文件是否存在

实现代码:

//说明:该方法检查fileId对应的文件在副屏是否存在,
第一个参数为之前发送文件时副屏返回的文件id,该id是所有的文件唯一的,第二个参数为结果回调
mDSKernel.checkFileExist(fileId, new ICheckFileCallback(){
  @Override
  public void onCheckFail() {}
 
  @Override
  public void onResult(boolean arg0) {
     //返回值为true是存在
     //返回值false是不存在  
      }                                    
});

4.4、清除副屏指定缓存文件

实现代码:

mDSKernel.deleteFileExist(FileID, new ICheckFileCallback() {
    @Override
    public void onCheckFail() {
        Log.d(TAG, "onCheckFail: ----------->");
    }

    @Override
    public void onResult(boolean exist) {
        Log.d(TAG, "onResult: ---------->" + exist);
    }
});

5、图片/视频显示规则

图片/视频的缩放规则:

  • 图片/视频默认居中等比例缩放,至少有两条边与显示容器边界接触;

图片/视频的建议分辨率:

  • 7寸:
  • 全屏显示图片:1024*600
  • 全屏显示视频:1024*600
  • 14寸:
  • 全屏显示图片/视频:1920*1080
  • 图片/视频+清单:1186*1080

建议文件大小:

  • 图片:≤1MB/张
  • 视频:≤5MB/个

建议幻灯片文件数量:

  • 图片:≤10张

建议轮播视频文件数量:

  • 视频:≤10个

注:因文件传送至副屏需要时间,显示文件时可能会有延时。故尽量在空闲时先将图片和视频文件缓存至副屏,当需要在副屏显示时,根据文件ID调用。

FAQ

1.App完成调试后如何大批量发布到每一台卖出去的机器?

答:平台合作伙伴通过合作伙伴平台将自己的应用上传到商米的应用库中,在我的应用市场中将该应用的自动安装功能打开即可实现。详情请查看开发者文档中的发布应用

2.如何防止别人改动自己铺出去的机器中的应用? 

答:合作伙伴可以在后台设置:1.只能通过应用市场安装应用到设备。2.需要通过手机号或邮箱在设备端获取调试权限调试设备。

3.什么是商米虚拟蓝牙打印?

答:商米基于物理链接实现的一个虚拟蓝牙链接,所有带打印的商米设备的蓝牙列表中会有一个名为’InnerPrinter’的蓝牙信号,对业务App来说和普通的蓝牙无区别,可以当成普通的蓝牙打印机。

4.你们的设备可以获取root权限吗?

答:不可以。为了设备的安全性,商米限制不能获取到设备的root权限。

5.商米的系统可以定制吗?

答:商米的优势就是,系统可控和可定制。包括系统的开机画面,系统桌面,默认启动App,系统默认安装应用都可以在合作伙伴后台设置。

6.如何认领应用?

答:当您在新建的应用时候发现如下提示,说明有合作伙伴已经上传过您的应用

如此应用确实为贵公司所有,请发邮件认领些应用。

认领邮件格式:

标题:认领应用

邮件正文:认领应用-同名应用(包名)  ,联系人,联系方式。

                  并附件中添加公司营业执照,对应软件著作权证明等

注意:这里的同名应用是指在商米平台上显示的应用名,可能与你的真实应用名不同

 

7.应用上传失败或无法上传?

答:如果你遇到解析失败等无法上传等信息,请确认APK编译打包正常,如有以上情况将会影响应用上架

1、APK编译时没有将ARM库文件包含在包文件中,此情况将导致无法正常安装。

2、APK编译时没有将应用签名打包在包文件中,此情况将导致签名异常。

3、APK编译时没有选择比上一版的版本号(versionCode),此情况将导致无法提交应用

 

 

跳转到应用商店APP详情

注:只适用于应用市场3.5或以上版本的竖屏设备

1.在应用内通过Intent打开:

public void startActivity(){

String packageName = "要打开的App的包名";

String uri = String.format("market://woyou.market/appDetail?packageName=%s",packageName);

        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));

        intent.addCategory(Intent.CATEGORY_DEFAULT);

        PackageManager packageManager = getPackageManager();

        List activities = packageManager.queryIntentActivities(intent,

                PackageManager.MATCH_DEFAULT_ONLY);

        boolean isIntentSafe = activities.size() > 0;

        if (isIntentSafe) {

            startActivity(intent);

        }

    }

2.通过使用webview打开:

在xml中使用进入应用市场详情页

如果mWebView.setWebViewClient();设置了此函数,则不能通过a链接自动打开,只能通过拦截链接再使用Intent跳转。

mWebView.setWebViewClient(new WebViewClient(){

            @Override

            public boolean shouldOverrideUrlLoading(WebView view, String url) {

                Log.d(TAG, "shouldOverrideUrlLoading: " + url);

                Intent intent = null;

                try {

                    intent = Intent.parseUri(url,Intent.URI_INTENT_SCHEME);

                } catch (URISyntaxException e) {

                    e.printStackTrace();

                    return false;

                }

                startActivity(intent);

                return true;

            }

        });

3.在浏览器打开:

地址:market://woyou.market/appDetail?packageName=包名

注:只能在终端自带浏览器或者Chrome 浏览器打开,其他浏览器均打开不开

特殊代码说明

一、获取商米设备标识

商米建议通过获取到以下内容来判断是否商米设备:

1.设备的品牌名 brand(如:SUNMI)

    商米的品牌名统一为 SUNMI

2.设备的系统型号 model(如:V1-B18)

    系统型号组成为 产品型号+硬件特性+‘-’+软件特性

    其中以V、M、P、L开头为手持设备,以T、D、S开头为横屏设备(截至2017年12月) 

3.设备的ROM版本号(如:1.1.0)。

4.设备的ROM顺序号(如:128)。

可以下载Demo,仿照Demo在自己项目src下面新建android.os包(固定写法),将SystemProperties.java放入该包下,按以下方法获取指定的值:

获取brand的代码为:

    String brand = SystemProperties.get("ro.product.brand");

获取model的方法为:

    String model = SystemProperties.get("ro.product.model");

获取ROM版本号的代码为:

    String versionname = SystemProperties.get("ro.version.sunmi_versionname");

获取ROM顺序号的方法为:

    String versioncode = SystemProperties.get("ro.version.sunmi_versioncode");

 

二、获取设备的SN号

1.在AndroidManifest.xml中添加如下权限。

2.在需要的地方用以下代码获取商米SN号。

public static String getSN() {
        if (Build.VERSION.SDK_INT ›= Build.VERSION_CODES.O) {
            return Build.getSerial();
        } else
            return Build.SERIAL;
    }

三、隐藏及恢复底部导航栏

(注:商米新开发了Kiosk霸屏模式,APP不用做任何修改,仅通过云端设置即可实现隐藏状态栏、导航栏并无法通过手势唤出。   该功能在T2、K1设备上已实现,其他设备上线进展请联系技术支持400-902-1168(9:00-21:00)。   建议合作伙伴使用商米Kiosk霸屏模式,已获得更好的体验)

Android系统默认提供了隐藏系统的导航栏的方法,但对于Dialog的支持较差,导致全屏对话框打开时先弹出导航栏再隐藏(闪屏),SunmiOS针对此问题进行了修复(V1系统固件版本252后支持,T1系统固件版本132后支持)

1.Activity的全屏化

——安卓默认支持

public class MainActivity extends AppCompatActivity {
    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        SystemUIUtils.setStickFullScreen(getWindow().getDecorView());
    }
}
public class SystemUIUtils {

    public static void setStickFullScreen(View view) {
        int systemUiVisibility = view.getSystemUiVisibility();
        int flags = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
                | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
        systemUiVisibility |= flags;
        view.setSystemUiVisibility(systemUiVisibility);
    }

}

2.Dialog的全屏化

——原生系统下 AOSP 的Bug会导致全屏对话框打开时先弹出导航栏然后再隐藏导航栏(闪屏)。

public AlertDialog create(boolean fullscreen) {
    LayoutInflater inflater = LayoutInflater.from(context);

    final AlertDialog dialog = new AlertDialog(context,
            R.style.DialogStyle);

    if(fullscreen){
        SystemUIUtils.setStickFullScreen(dialog.getWindow().getDecorView());
    }
}

3.设置隐藏后显示导航栏

3.1.全局底部上划

——上划后底部导航栏显示4s,4s后底部导航栏隐藏

3.2.切换应用至其他APP(如APP内跳转至第三方应用、第三方APP弹窗等)

——切换至其他APP是否显示底部导航栏以第三方应用要求为准,切换至自己APP时底部导航栏消失

四、避免重复申请外设权限

当APP需要通过USB关联外设来实现业务时(比如连接USB打印机打印小票),安卓要求用户手动确认设备使用权限,来保障用户信息安全,防止木马非法入侵USB设备。

1.如何避免USB设备重新插拔同一个外设时,APP反复申请该外设权限

需要用户手动确认时,勾选“默认情况下用于该USB设备”,无法通过代码绕过该安全机制

验证DEMO

2.如何避免设备重启后,已勾选“默认情况下用于该USB设备”的APP仍反复申请同一个外设的使用权限

2.1.在APP的AndroidManifest中指定某个Activity部分中,添加如下代码


                


2.2.在该目录下创建xml文档



    

其中,class的值需要和要连接的外设类型一致,外设class参照表如下


    /**
     * USB class indicating that the class is determined on a per-interface basis.
     */
    public static final int USB_CLASS_PER_INTERFACE = 0;
    /**
     * USB class for audio devices.
     */
    public static final int USB_CLASS_AUDIO = 1;
    /**
     * USB class for communication devices.
     */
    public static final int USB_CLASS_COMM = 2;
    /**
     * USB class for human interface devices (for example, mice and keyboards).
     */
    public static final int USB_CLASS_HID = 3;
    /**
     * USB class for physical devices.
     */
    public static final int USB_CLASS_PHYSICA = 5;
    /**
     * USB class for still image devices (digital cameras).
     */
    public static final int USB_CLASS_STILL_IMAGE = 6;
    /**
     * USB class for printers.
     */
    public static final int USB_CLASS_PRINTER = 7;
    /**
     * USB class for mass storage devices.
     */
    public static final int USB_CLASS_MASS_STORAGE = 8;
    /**
     * USB class for USB hubs.
     */
    public static final int USB_CLASS_HUB = 9;
    /**
     * USB class for CDC devices (communications device class).
     */
    public static final int USB_CLASS_CDC_DATA = 0x0a;
    /**
     * USB class for content smart card devices.
     */
    public static final int USB_CLASS_CSCID = 0x0b;
    /**
     * USB class for content security devices.
     */
    public static final int USB_CLASS_CONTENT_SEC = 0x0d;
    /**
     * USB class for video devices.
     */
    public static final int USB_CLASS_VIDEO = 0x0e;
    /**
     * USB class for wireless controller devices.
     */
    public static final int USB_CLASS_WIRELESS_CONTROLLER = 0xe0;
    /**
     * USB class for wireless miscellaneous devices.
     */
    public static final int USB_CLASS_MISC = 0xef;
    /**
     * Application specific USB class.
     */
    public static final int USB_CLASS_APP_SPEC = 0xfe;
    /**
     * Vendor specific USB class.
     */
    public static final int USB_CLASS_VENDOR_SPEC = 0xff;

3.如何避免前两步后设置后,业务页面会因USB设备插入而刷新

安卓原生逻辑导致用户选择“默认情况下用于该USB设备”会导致USB设备插入时打开某个指定activity。如果要避免页面刷新导致业务中断,可以增加如下代码防止页面刷新。

 

五、如何避免自己的应用数据被清除

应用数据默认是可以通过系统设置删除的,删除后应用将恢复刚安装的状态。但是可以通过配置APP来精细管理应用数据(比如按照业务分类或时间删除数据),也可以避免重要的应用数据被删除。

Screenshot-3.png
Screenshot-2.png

在程序的manifest文件的application中加上manageSpaceActivity属性,并且指定一个Activity,这个Activity就是点击管理空间之后会跳转的那个Activity了。

  

  
    
  

PS 如果要避免数据被删除,可以创建一个自动关闭的Activity。

public class ManageSpaceActivity extends Activity {  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
  
        finish();  
  
    }// onCreate  
}  

七、如何避免插入usb外设导致app界面闪烁

在APP的AndroidManifest中,添加如下代码

android:configChanges="navigation|keyboardHidden|keyboard"

八、如何避免扫描枪扫出内容与实际不一致

一般情况下,文本框输入的所有内容会提交给输入法进行转换(如拼音、联想等),有的输入法经过转换后会转义部分字词导致输入内容不一致。

除了更换系统输入法为合适的输入法外,还可以利用输入法通常不对密码框进行处理的规则,调整文本框类型为“可见密码”(inputType),并且约定好可输入规范(digits)也可以避免该问题

发布应用

关于商米应用市场

每个商米合作伙伴都可以在商米官网 注册商米合作伙伴帐号,有一个自己的操作后台,原则上每一台商米的设备在卖出去的时候都会和一个合作伙伴账号绑定,商米会以合作伙伴为粒度提供部分功能和权限的控制服务。

SUNMIUI内部有一个应用市场,合作伙伴可以通过应用市场将自己的应用大规模分发到商米的设备上。部分合作伙伴可以有一个自己管理的应用市场,如果合作伙伴没有在后台打开’允许第三方应用安装'(如下图箭头所示),默认旗下用户只能通过商米应用市场为设备安装应用。

1

应用市场规则

商米为部分合作伙伴分配了不同权限的应用市场,默认合作伙伴上传的应用不用商米审核就可以出现在自己的应用市场中。商米可以审核该应用,通过审核后该应用会出现在其他合作伙伴的应用市场中,当然部分合作伙伴可以选择自己的应用不出现在其他人的应用市场,也可以选择自己应用市场不出现其他人的应用,关于权限的授予可以咨询商米客服400-902-1168(每天9:00~21:00,节日除外)

应用分发流程

应用开发完成后,上传到应用市场,用户可以在设备上的应用市场搜索到应用,如果是渠道合作伙伴可以设置应用在旗下的设备自动安装。以下讲解应用首次上传分发的流程。

1.上传应用。

在 合作伙伴后台,进入”我的应用”选项,点击创建应用

2

2.填写相关内容

点击’上传安装包’,从文件夹中选择您要上传的apk,等待上传完成后,商米后台将自动分析安装包,下图绿色箭头指示部分将会自动填写好,开发者手动填写应用介绍,应用适配的设备,应用类型等信息。

  • 上传APK安装包时如果提示包名重复,则表示此APK已经在商米应用市场上架,如果此APK是贵公司所有,请联系商米客服400-902-1168(每天9:00~21:00,节日除外),进入认领。
3

3.提交应用。

合作伙伴将剩余部分内容补充完整后,下图的’提交’按钮将由灰色不可点击变成绿色可点击状态,点击’提交’将会完成应用的上传,

4

关于灰度部署

上图中’提交’按钮上方有一个’是否灰度部署’选项,勾选后提交按钮将变成’灰度部署’项,点击后将进入灰度部署配置项中。

灰度部署是商米针对合作伙伴的实际需求提供的一项功能,每台设备在使用的时候会有一个地理理位置信息,合作伙伴在开发好自己的应用适配商米的设备后,可以根据地理位置或者SN号在部分设备上部署,只有灰度部署范围内的设备的应用市场中才会出现该应用。在小范围的灰度部署后,开发者可以将应用部署到全范围设备上。

5

设置自动安装。

如果希望上传的应用自动在旗下所有设备上安装,可以在’我的应用市场’项中设置该应用为自动安装应用(如下图箭头所示),旗下设备在收到推送通知后会自动下载安装该应用,设置自动安装后还可以设置该应用是否作为默认启动应用(如下图箭头所示),设备开机后将自动启动该应用。

6

更新应用

在应用通过商米审核后,在我的应用中列表项中会有’更新’按钮,开发者可以发布该应用的升级包。点击后进入更新应用页,更新的流程和发布流程类似,请参照上面的发布流程。

  • 上传更新包的时候如果提示签名异常,请检查APK签名是否与原来的安装包不一致。如确认是需要变更签名,则请删除应用后再创建应用
  • 上传更新包的时候如果提示包名不一致,请检查APK包名是否有变动或有没有上传错误。如确认是需要变更包名,则请删除应用后再创建应用
7

调试设备

关于商米设备调试

(本教程仅适用于非金融设备,如需要开启SunmiP系列设备的调试权限,请联系销售人员)

默认商米的设备插上USB线就可以调试,商米也提供了设备调试权限的控制功能,合作伙伴在后台开启调试权限控制后(如下图所示),则需要通过邮箱或手机号获取调试权限才能调试该设备。如果不能直接调试,请查看合作伙伴后台是否开启了该权限。

1

注:调试权限的控制只针对绑定了合作伙伴(渠道)的设备生效。

启用调试权限控制后如何调试设备

如果合作伙伴启用了’调试权限控制’,则开发者需要在旗下设备上通过邮箱和手机号获取调试权限才能调试设备,合作伙伴可以在商米合作伙伴平台添加调试员(开发者)的手机号或邮箱。以下为获取设备调试权限的歩骤:

1.添加调试员。

开发者调试设备之前请了解设备所属的渠道,可以找自己公司相关的管理人员询问。管理人员需要在商米的合作伙伴平台后台添加调试员的手机号或邮箱。

2

2.插入USB(V1s、V2、M2、L2建议暂时通过关闭云端调试保护开关来调试设备)

确保自己有调试权限的手机号或邮箱后,将设备连接电脑,建议开发者在windows下调试,如果手机能被电脑正确识别,通常会出现如下的弹窗提示:

3

如果设备没有被电脑识别,请确认是否因以下原因引起。

  • 接触不良,请多次插拔扭动USB接口确认。
  • 数据线故障,换条数据线看下能不能识别。
  • 电脑没有安装移动设备驱动,可以使用第三方工具软件安装。

3.获取验证码。

点击上面的“我要调试”项,将进入手机号或邮箱验证权限步骤,同时设备的USB调试模式将自定开启(这里是指基础的调试模式,不是权限);点击“知道了”将退出弹窗,不会开启USB调试模式。

4

4.验证权限

输入之前添加的手机号或邮箱后点击’获取验证码’,商米将向手机号或邮箱发送验证码,填入验证码点击’授权并开启调试’。

5

5.打开权限。

打开权限后,可以查看logcat中有无输出判断是否可以调试设备。

6

应用市场外部调用SDK接口

简介

应用市场提供一些接口供其他APP调用,具体功能包括 跳转应用详情、跳转更新列表、跳转评论弹框

Gradle依赖方式

implementation ‘com.sunmi:appstorelib:1.0.0’

跳转应用详情

/**  
 * @param context 上下文对象  
 * @param packageName 包名    
 */ 
SunmiAppStoreUtil.startAppDetail(getApplication(), "App包名");

跳转更新列表

SunmiAppStoreUtil.startAppUpdate(getApplication());

跳转评论弹框

/**
* @param context 上下文对象
* @param packageName 包名
* @param type true 主动点击弹出 false 打开自动检测弹出
*/
SunmiAppStoreUtil.startAppComment(getApplication(), "App包名", true);

以下老版本调用方式已过时,不建议使用

1.     在应用内通过Intent打开:(已过时)

public void startActivity(){ String packageName = "要打开的App的包名";

String uri = String.format("market://woyou.market/appDetail?packageName=%s",packageName);

Intent intent= new Intent(Intent.ACTION_VIEW,Uri.parse(uri));

intent.addCategory(Intent.CATEGORY_DEFAULT);

PackageManager packageManager = getPackageManager();

List activities = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);

    boolean isIntentSafe = activities.size() › 0;

if (isIntentSafe) {

startActivity(intent);

}

 }

2.     通过使用webview打开: 在xml中使用< a href=" ">进入应用市场详情页 如果mWebView.setWebViewClient();设置了此函数,则不能通过a链接自动打开,只能通过拦截链接再使用Intent跳转。(已过时)

mWebView.setWebViewClient(newWebViewClient() {

@Override public boolean shouldOverrideUrlLoading (WebView view, Stringurl){

Log.d(TAG, "shouldOverrideUrlLoading: " + url);

Intent intent = null;

try {

intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);

} catch (URISyntaxException e) {

e.printStackTrace();return false;

}

startActivity(intent);

return true;

}

});

3.在浏览器打开: 地址:market://woyou.market/appDetail?packageName=包名 注:只能在终端自带浏览器或者Chrome 浏览器打开,其他浏览器均打不开(已过时)

4.更新 appUpdate market://woyou.market/appUpdate (已过时)

5.评论 appComment market://woyou.market/appComment?packageName=%s&type=true/false(true主动点击弹出 false 自动弹出) (已过时)

String uri = String.format(“market://woyou.market/appComment?packageName=%s&type=%s”,packageName,true);

应用检测规范

介绍

     为了商米生态链中包括软件开发者,渠道伙伴,代理商,终端用户等所有合作伙伴提供统一,专业,优质的应用。我们将根据一些原则和规范对所有在商米应用市场收录的应用进行评估。

一、应用基本信息标准

 1、应用名称应与安装到机器上桌面显示的名称一致

 2、应用介绍和更新说明与实际功能相符

 3、应用介绍和更新说明不能含有危害国家安全、低俗、情色等非法内容。

 4、应用截图图片清晰不模糊、无水印、竖图竖放、横图横放,所有截图方向需一致

 5、应用如需登录使用,需在备注栏中填写测试账号,如无登录功能,则在备注中填写:无登录功能

二、应用功能标准

  1、应用启动和运行时没有奔溃现象

  2、应用可以正常安装或卸载

  3、应用页面排版合理,适配横竖版屏蔽,无拉伸比例失调现象

  4、应用功能模块划分清晰,业务流畅,功能完整。

三、应用安全标准

  1、应用申请的权限和其实际功能不符

  2、应用消耗过多的网络流量

  3、应用不存在病毒代码特征

  4、应用没有ROOT代码特征

  5、应用使用过程中不得频繁弹出悬浮窗广告,中断用户操作,影响用户体验

四、金融安全标准

  1、根据银联规范要求在金融机具上架的应用,不得申请以下权限

权限类型 权限内容 备注
金融权限 android.permission.ACCESS_LOCATION_EXTRA_COMMANDS 允许应用程序访问额外的位置提供命令
金融权限 android.permission.ACCESS_SURFACE_FLINGER 平台上底层的图形显示支持,一般用于游戏或照相机预览界面和底层模式的屏幕截图
金融权限 android.permission.BRICK 能够禁用手机,非常危险,顾名思义就是让手机变成砖头
金融权限 android.permission.CHANGE_COMPONENT_ENABLED_STATE 改变组件是否启用状态
金融权限 android.permission.CHANGE_CONFIGURATION 允许一个程序修改当前设置,如本地化
金融权限 android.permission.CELL_PHONE_MASTER_EX 手机优化大师扩展权限
金融权限 android.permission.DELETE_PACKAGES 允许程序删除应用
金融权限 android.permission.DISABLE_KEYGUARD 允许程序禁用键盘锁
金融权限 android.permission.INJECT_EVENTS 允许一个程序截获用户事件如按键、触 摸、轨迹球等等到一个时间流
金融权限 android.permission.MASTER_CLEAR 允许程序执行软格式化,删除系统配置信息
金融权限 android.permission.MODIFY_AUDIO_SETTINGS 修改声音设置信息
金融权限 android.permission.MOUNT_FORMAT_FILESYSTEMS 格式化可移动文件系统,比如格式化清空SD卡
金融权限 android.permission.PROCESS_OUTGOING_CALLS 允许程序监视,修改或放弃播出电话
金融权限 android.permission.READ_FRAME_BUFFER 读取帧缓存用于屏幕截图
金融权限 android.permission.READ_LOGS 允许程序读取底层系统日志文件
金融权限 android.permission.READ_OWNER_DATA 允许程序读取所有者数据
金融权限 android.permission.READ_SMS 允许程序读取短信息
金融权限 android.permission.REBOOT 允许程序重新启动设备
金融权限 android.permission.SET_PROCESS_FOREGROUND 允许程序当前运行程序强行到前台
金融权限 android.permission.SET_PROCESS_LIMIT 允许程序设置最大的进程数量的限制
金融权限 android.permission.SET_TIME 设置系统时间
金融权限 android.permission.SET_TIME_ZONE 设置系统时区
金融权限 android.permission.SET_WALLPAPER 允许程序设置壁纸
金融权限 android.permission.SET_WALLPAPER_HINTS 允许程序设置壁纸hits
金融权限 android.permission.SIGNAL_PERSISTENT_PROCESSES 允许程序请求发送信号到所有显示的进程中
金融权限 android.permission.WRITE_CALENDAR 写入日程,但不可读取
金融权限 android.permission.WRITE_CALENDAR 写入联系人,但不可读取
金融权限 android.permission.WRITE_SECURE_SETTINGS 允许程序读写系统安全敏感的设置项
金融权限 android.permission.WRITE_SETTINGS 允许程序读取或写入系统设置
金融权限 android.permission.SHUTDOWN 关机
金融权限 android.permission.ACCESS_FINE_LOCATION 通过GPS芯片接收卫星的定位信息,定位精度达10米以内
金融权限 android.permission.SYSTEM_ALERT_WINDOW 显示系统窗口
金融权限 android:allowBackup=”false” 允许应用数据的备份和恢复,该属性需要配置为
金融权限 android:debuggable=”true” 允许debug模式
金融权限 android:exported=”true” 允许被另一个Application的组件启动
金融权限 android:sharedUserId=”android.uid.system” 允许程序运行在系统进程中(系统权限运行)