Categories
Android

Sqaure 类产品在Android 机型上的适配问题

我们已经公布了 盒子支付客户端支持机型列表 ,而Sqaure 也很早在他们帮助中心提示用户他们对Android 设备的兼容情况。很多用户很关心他们的机型为什么不支持。

众所周知,Android碎片化的问题确实困扰的不少的应用开发商,Animoca 公司最近也大倒苦水,他们购买了400多款手机对应用进行质量测试。

有人问,为何Square与国内的一些公司支持的 Android 机型比你们更多?因为他们跟盒子支付的音频数据方式完全不一样,他们传输数据量小,通讯不加密且只能单向通讯。实现原理比较简单:通过MIC口实现数据接收,使用 android.media.AudioRecord 实现录音,并对采集下来的模拟信号进行处理。主流的音频通讯速率 1.1k/2.2K,传输的数据有限,对于数据量大,也就需要做更多的校验,保证数据传输正确。

那难点在哪呢?

一、硬件问题
1. Android 厂商硬件配置不一致,需要购买很多机型一个个去调试。
2. 音频口供电电压高低不一,电压不够带动驱动的,需要加上电池供电。
3. 音频口引脚接线也尽不相同。像 moto xt800,有些厂家针对这款机器在硬件上做了适配。
4. 录音得出的波形不满足要求,录音失真,回路干扰等。
5. 音频口硬件接触不良,插口比较宽或比较深。
6. 输出过大。

二、软件问题
厂商修改 framework 层代码。一些手机原装ROM可以跑,但刷了第三方ROM就出现了问题。传说中的小米手机统常出现线程操作问题,乱跑。

Categories
Android

Android 屏蔽线控耳机

研究了Square Android 应用,它在刷卡界面监测耳机按钮事件,全部屏蔽掉线控事件。

MediaButtonDisabler.java:

package org.lytsing.square;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;

public class MediaButtonDisabler extends BroadcastReceiver {

    private static final String TAG = "MediaButtonDisabler";

    private static final BroadcastReceiver INSTANCE = new MediaButtonDisabler();

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "Intercepted media button.");

        abortBroadcast();
    }

    public static void register(Context context) {
        IntentFilter filter = new IntentFilter(Intent.ACTION_MEDIA_BUTTON);
        filter.setPriority(Integer.MAX_VALUE);
        context.registerReceiver(INSTANCE, filter);
    }

    public static void unregister(Context context) {
        context.unregisterReceiver(INSTANCE);
    }
}

简要说明,设置过滤优先级为最高,int 的最大值(2^32 – 1 = 2147483647),这样,就会在第一时间把线控事件的广播中止,使其他应用无法接收到。

在Activity 调用:

    @Override
    protected void onPause() {
        super.onPause();
        
        MediaButtonDisabler.unregister(this);
    }
    
    @Override
    protected void onResume() {
        super.onResume();
        
        MediaButtonDisabler.register(this);
    }

另外,我们发现一些Android手机在音频口插入我们的盒子读卡器后,音乐播放器乱放,反复播放又暂停又播放,开始以为是线控问题,屏蔽掉后,问题依旧。有做这方便的朋友,可以交流交流,分享一下。

Categories
Android

Android 组件: SectionedAdapter

这个组件与Mark Murphy 的书《The Busy Coder’s Guide to Advanced Android Development》有一些不同,这个SectionedAdapter是通过干坏事,反编译Vending.apk得到的。还有一个是AggregatedAdapter,合起来实现market单个软件信息显示的效果。
android market asset info

不过AggregatedAdapter比较难反编译出来,没关系,加上cwac-merge这个组件,就可以实现同样的效果。不同分段的Adapter,继承SectionedAdapter,实现自己的东西,该干啥就干啥,然后merge起来。

SectionAdapter.java

/*
 * Copyright (C) 2010 lytsing.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package org.lytsing.adapters;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

/**
 * abstract SectionAdapter, difference from
 * cw-advandroid/ListView/Sections/src/com/commonsware/android/listview/SectionedAdapter.java
 * just decompile from Vending.apk :-)
 *
 */
abstract public class SectionAdapter extends BaseAdapter {
    protected int mCount;
    protected boolean mDeactivated;
    protected View mSectionHeaderView;

    /**
     * Constructor
     * 
     * @param sectionTitleId The resource ID for a layout file containing a layout to use when
     *                  instantiating views.
     * @param context The current context.
     * @param parent The parent that this view will eventually be attached to
     */
    public SectionAdapter(int sectionTitleId, Context context, ViewGroup parent) {
        this(Util.inflateView(R.layout.section_header, context, parent));
        ((TextView)mSectionHeaderView).setText(sectionTitleId);
    }

    /**
     * Constructor
     * 
     * @param sectionHeaderView The header view
     */
    public SectionAdapter(View sectionHeaderView) {
        mSectionHeaderView = sectionHeaderView;
        mDeactivated = false;
        mCount = 0;
    }

    public void activate() {
        if (mDeactivated) {
            mDeactivated = false;
            notifyDataSetChanged();
        }
    }

    /**
     * Are all items in this SectionAdapter enable?
     * If yes it means all items are selectable and clickable.
     */
    public boolean areAllItemsEnabled() {
        return false;
    }

    public void deactivate() {
        if (mDeactivated == false) {
            mDeactivated = true;
            notifyDataSetChanged();
        }
    }

    /**
     * How many items are in the data set represented by this
     * Adapter.
     */
    public int getCount() {
        if (mDeactivated) {
            return 0;
        } else {
            return mCount + 1; // add one for header
        }
    }

    /**
     * Get the data item associated with the specified
     * position in the data set.
     * 
     * @param position Position of the item whose data we want
     */
    public Object getItem(int position) {
        if (position == 0) {
            return mSectionHeaderView;
        }
        
        return null;
    }

    /**
     * Get the row id associated with the specified position
     * in the list.
     * 
     * @param position Position of the item whose data we want
     */
    public long getItemId(int position) {
        if (position == 0) {
            return mSectionHeaderView.getId();
        } 
        
        return 0;
    }

    /**
     * Get a View that displays the data at the specified
     * position in the data set.
     * 
     * @param position Position of the item whose data we want
     * @param convertView View to recycle, if not null
     * @param parent ViewGroup containing the returned View
     */
    public View getView(int position, View convertView, ViewGroup parent) {

        return position == 0 ? mSectionHeaderView : null;
    }

    /**
     * Returns true if the item at the specified position is not a separator
     * (A separator is a non-selectable, non-clickable item).
     * 
     * @param position Index of the item
     * @return True if the item is not a separator
     */
    public boolean isEnabled(int position) {
        return false;
    }
}

Util.java

/*
 * Copyright (C) 2010 lytsing.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.lytsing.adapters;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

/**
 * Util 
 *
 */
public class Util {

    private Util() {
    }
    
    /**
     * Inflate a new view hierarchy from the specified XML resource.
     * 
     * @param resource ID for an XML layout resource to load 
     * @param context The current context.
     * @return The root View of the inflated XML file.
     */
    public static View inflateView(int resource, Context context) {

        return inflateView(resource, context, null);
    }

    /**
     * Inflate a new view hierarchy from the specified xml resource.
     * 
     * @param resourceID for an XML layout resource to load (e.g.,
     * @param context The current context.
     * @param parent simply an object that provides a set of LayoutParams
     *        values for root of the returned hierarchy
     * @return The root View of the inflated XML file.
     */
    public static View inflateView(int resource, Context context, ViewGroup parent) {
        LayoutInflater vi = (LayoutInflater)context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        return  vi.inflate(resource, parent, false);
    }
}

res/layout/section_header.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:textAppearance="?android:attr/textAppearanceSmall"
    android:textStyle="bold"
    android:gravity="center_vertical"
    android:background="@android:drawable/dark_header"
    android:paddingLeft="8.0dip"
    android:layout_width="fill_parent"
    android:layout_height="26.0dip"
    >
</TextView>

代码下载; https://github.com/lytsing/SectionedAdapter
参考阅读:
Jeff Sharkey’s blog : Separating Lists with Headers in Android 0.9
androidguys: CWAC’d Up: Alternative Adapters
本博客文章: 千万不要把Listview放在ScrollView里

Categories
Android

Android设置显示本机号码

本机号码,我觉得是没有多大的用处,几乎谁都会知道自己的手机号码,但也有用户真的需要这个功能。有个对讲机软件tikl – touch to talk,可以通过网络进行多方通话,需要用到本机号码。 测试短信的脚本,自己给自己发短信,也需要本机号码。

Android手机在 设置-关于手机-状态消息,可以查看到本机号码,很多情况是显示未知。 很多模组用AT+CNUM 这组AT命令可以设置与查询本机号码。一般的手机在查看本机号码的界面都可以添加本机号码的,比如MTK/展信手机都提供这个功能。 Android对卡的操作,都是直接读写卡上的信息,用比较底层的AT+CRSM,而不是常用的AT,比如 AT+CMBR, AT+CMBW,AT+CNUM等。

Android手机设置本机号码,目前我还没发现有工具可以设置,很多人是把SIM卡拿到可以设置的手机上设置,然后把卡重新插上。 不过,Froyo提供了内部接口方法Phone.setLine1Number(),CM6的通话设置里就加入了本机号码设置功能:

具体的代码,在这里看清清楚楚:

http://github.com/CyanogenMod/android_packages_apps_Phone/commit/5351ce8247eb9fc9a3bf2ec751d14dcd373ab92e

可能需要到github注册个帐号,才可以浏览代码,主要是在phone里面添加处理,独立写个小程序的话,比较麻烦,因为要访问PhoneApp。

更新: (2010-11-15) 今天试了一下,参考Settings, 共享一个sharedUserId,也可以写个独立的程序,在g1 2.2,HTC desire上测试通过。注意:Android 2.2以下的无法使用。

代码献上: https://github.com/lytsing/MyPhoneNumber

Categories
Android

小探 android版QQ空间(体验版)

STONE说tx早有android版 QQ空间了,于是下载看看,感觉还行,功能太少了。不甘心,看看里面有什么好玩的,不会是个wrapper web的吧,于是决定反编译看个究竟。虽然大家都在骂腾讯,但不可否认的是,他们产品的用户体验做的很好, 搞android开发,也可以看看他们产品,好的界面设计,我们也可以拿来用。前文已经介绍了一些反编译apk的工具,搞软件开发的,至少有那么一点hack精神,学会反编译apk是Android应用程序开发的一项基本技能。

UI

界面是淡蓝色风格,也是我比较喜欢的风格,美工图片作图比较到位。看看下边的Tab吧,跟web一样了.

android qzone 体验版

开始琢磨一下,到底用什么东东做的?原来是自定义的tabTextStyle,里面还有非常多自定义的view, style,正应那句话:“优秀的组件都是自定义的”。

personcenterheaderview.xml :

<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout
    android:orientation="vertical"
    android:background="@drawable/homepageheadbg"
    android:focusable="false"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <LinearLayout
        android:orientation="horizontal"
        android:id="@id/LinearLayoutUserMood"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1.0">
        <LinearLayout
            android:gravity="center"
            android:background="@drawable/personhead"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="4.0px"
            android:layout_marginTop="5.0px">
            <ImageView
                android:id="@id/ImageViewUserIcon"
                android:layout_width="49.0dip"
                android:layout_height="49.0dip"
                android:scaleType="centerCrop" />
        </LinearLayout>
        <LinearLayout
            android:orientation="vertical"
            android:id="@id/saynamegroup"
            android:background="@drawable/saydialog"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="8.0px"
            android:layout_marginRight="10.0px"
            android:layout_weight="1.0">
            <TextView
                android:textSize="14.0px"
                android:textColor="@color/black"
                android:ellipsize="end"
                android:id="@id/TextViewMood"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="2.0px"
                android:layout_marginRight="2.0px"
                android:maxLines="2"
                android:layout_weight="1.0"
                />
            <TextView
                android:textSize="12.0px"
                android:id="@id/TextViewTime"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="2.0px"
                android:layout_marginTop="2.0px"
                style="@style/unnoticeableTextStyle"
            />
        </LinearLayout>
    </LinearLayout>
    <LinearLayout
        android:gravity="center"
        android:orientation="horizontal"
        android:id="@id/LinearLayoutTabNav"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">
        <TextView
            android:gravity="center"
            android:id="@id/TextViewTabMood"
            android:layout_width="wrap_content"
            android:layout_height="36.0dip"
            android:text="心情"
            android:layout_weight="1.0"
            style="@style/tabTextStyle" />
        <TextView
            android:gravity="center"
            android:id="@id/TextViewTabBlog"
            android:layout_width="wrap_content"
            android:layout_height="36.0dip"
            android:text="日志"
            android:layout_weight="1.0"
            style="@style/tabTextStyle" />
        <TextView
            android:gravity="center"
            android:id="@id/TextViewTabAlbum"
            android:layout_width="wrap_content"
            android:layout_height="36.0dip"
            android:text="相册"
            android:layout_weight="1.0"
            style="@style/tabTextStyle" />
        <TextView
            android:gravity="center"
            android:id="@id/TextViewMessage"
            android:layout_width="wrap_content"
            android:layout_height="36.0dip"
            android:text="留言板"
            android:layout_weight="1.0"
            style="@style/tabTextStyle" />
    </LinearLayout>
</LinearLayout>

数据交换

采用对象序列化,那么提供api的服务端也是用java写的了。

代码组织

搞了很多封装,写了N多个组件,不过看起来还清晰。

命名规则

不知道tencent有没有公司内部的规范文档,但我看到

zhuye1.png
zhuye2.png
zhuye3.png

android:id=”@id/ImageView01″
android:id=”@id/ImageView02″

之类的,感到很失望,浪费我时间来翻尸体。

国际化

在layout xml文件还出现
android:text=”上传”,android:text=”取消” android:text=”请输入验证码” 之类的东西,为什么不用@string/xxx 呢?

res/values/strings.xml 里写了很多中文,已经有了values-zh目录,为什么不写到里面呢?

Pages: Prev 1 2 3 Next