Categories
Android

解决android 2.2 market搜索结果偏少的问题

那是很久很久以前的事了,大概花了两个星期去折腾,整理整理,与朋友们分享一下。

我们开始移植到 2.2时,market运行很好,过一段时间测试组提出bug。问题现象:Android market 搜素记录严重偏少,比如搜索 qq,在我们的broncho a1上,只有1条记录。

首先,确定apk包是没有问题的。同样的包安装在G1上,可以找到300多条记录。此外,特意让上海龙旗的一位网友看了他们公司的手机,说可以搜索到25条qq记录,他们的分辨率是 800 * 600, 我们的是480 * 320。他把apk包发给我,在我们机器上还是同样的结果。

其次,在javaeye上,有网友说跟机器有 root权限有关,他发布一款软件到market上,他的机器是root权限,搜索不到他刚发布的程序,改为普通用户后,就可以了。这有可能,因为我们的机器一直是root权限,重新编译kernel,改为普通用户,还是不行。

后来有人说,跟market上设置的protected权限有关。我为此还花了25$注册 market开发者,写个小程序放上去,关闭权限,结果还是没有生效。

解决问题最好的方法是找差异。为此,getprop 获取G1系统参数做比较,还是没有找到有异常嫌疑之处。gpps包是从cm上获取的,我们没有通过正规的渠道拿到,怀疑是跟授权有关,如果真的是这样,那就没办法了,不过,上海龙旗既然可以搜索到25条记录,我想应该跟这没关系。

用 tcpdump与 wireshark 工具观察 market请求发出的数据包找出url,那是一串经过序列化然后再经过base64加密后的字符串。有开源的 android market api,基于protobuf协议,之前也是运行好好的,最近就不行了。翻遍它的 google groups,说什么帐号跟手机的deviceId挂钩了,我重新注册个gmail帐号,还是一样。又有说根据ip来源,返回不同的搜索结果。我干脆把程序放在美国的服务器上跑,还是没成功。

绝望,绝望!!! 一个android手机,如果电子市场不能使用,那简直是暗淡无光。

正值网络上爆出android暗含高达40% 的扣费短信。wusong童鞋无聊给我发个帖子链接《近期 Android 平台扣费软件检测的分析心得与思路分享》,说是如何扫描apk包含扣费短信的,文章中说通过反编译apk,插入 Log.v 打印出非法信息。灵光一闪,既然我无法全部理解那长长的url,那何不如让它自己打印出自己的信息呢?

修改ProtoBuf里的insertObject函数,添加Object toString()打印对象,如果是vector,就遍历打印。

结果发现,我们只有这个信息:

1222 insertObject: W tag=10 index=0com.google.android.feature.GOOGLE_BUILD

相应的g1的有很多信息,经查询, 问题出现在 FeatureInfo[] systemAvailableFeatures =
packageManager.getSystemAvailableFeatures(); 这个函数。

运行时,PackageManagerService.java 从 /system/etc/permissions/ 下所有
的文件读取 features/permissions。这个目录下,我们只有一个features.xml, 而这个文件还是从gapps里拷贝过来的。
所以,应该把以下文件

frameworks/base/data/etc:
android.hardware.camera.autofocus.xml
android.hardware.location.gps.xml
android.hardware.telephony.gsm.xml
android.hardware.touchscreen.multitouch.xml
android.hardware.wifi.xml
handheld_core_hardware.xml

生成image时拷贝到

/system/etc/permissions/

如果是cdma,
请把 android.hardware.telephony.gsm.xml
替换为
android.hardware.telephony.cdma.xml

测试,OK。

我把问题解决后,才发现一下别人的帖子:
APAD IMX515 安装 Android Market 方法

Working Android market and all google app (for 2.2)

问题解决后再回顾:

1. 如果我早用 Quick system info 这个工具,会很快找到问题,这个工具可以显示 system availabe features信息,两机器都同时安装,跟G1一对比,一目了然。

2. 如果把 market的数据清空,再抓数据包,在 base64 decode,虽然不能看出所有的含义,但至少也看到 xxx.xx.xx 这样可显示的字符串,也可以尽快找出问题。market 手机系统信息,是第一次运行时才收集,后面的url请求只需要从cache里获取系统信息即可。

3. DeviceConfiguration.java 有 toString(),注入调试信息也可以显示出来。

4. 仔细阅读官方文件 http://developer.android.com/guide/appendix/market-filters.html, 有提到getSystemAvailableFeatures()这个函数.

Categories
Android

直接修改Market包显示支持收费软件,首页推荐软件

目前用MarketEnable,MarketAccess 之类的软件可以做到,但也有缺点:被修改apn,重启后无效。

大陆第三方ROM,一般都直接修改了电子市场包,观察一下安卓网自制的ROM,build.prop中都多了两行代码:

hiapk.vending.operator.name=T-Mobile
hiapk.vending.operator=310260

把运营商改为美国的。然后用

SystemProperties.get("hiapk.vending.operator.name");
SystemProperties.get("hiapk.vending.operator");

替换

TelephonyManager.getSimOperatorName();
TelephonyManager.getSimOperator()

下面是我修改的2.2.7版本的patch:

$ colordiff Vending_o/smali/com/android/vending/VendingApplication\$ApiClientContext.smali Vending/smali/com/android/vending/VendingApplication\$ApiClientContext.smali 
239,245c239
<     iget-object v2, p0, Lcom/android/vending/VendingApplication$ApiClientContext;->this$0:Lcom/android/vending/VendingApplication;
< 
<     invoke-static {v2}, Lcom/android/vending/VendingApplication;->access$100(Lcom/android/vending/VendingApplication;)Landroid/telephony/TelephonyManager;
< 
<     move-result-object v2
< 
<     invoke-virtual {v2}, Landroid/telephony/TelephonyManager;->getSimOperatorName()Ljava/lang/String;
---
>     const-string v2, "T-Mobile"
247c241
<     move-result-object v7
---
>     move-object/from16 v7, v2
259,265c253
<     iget-object v2, p0, Lcom/android/vending/VendingApplication$ApiClientContext;->this$0:Lcom/android/vending/VendingApplication;
< 
<     invoke-static {v2}, Lcom/android/vending/VendingApplication;->access$100(Lcom/android/vending/VendingApplication;)Landroid/telephony/TelephonyManager;
< 
<     move-result-object v2
< 
<     invoke-virtual {v2}, Landroid/telephony/TelephonyManager;->getSimOperator()Ljava/lang/String;
---
>     const-string v2, "310260"
267c255
<     move-result-object v9
---
>     move-object/from16 v9, v2

重新打包,签名即可。

Categories
Android

Android Market 之 deviceId

有时候我们在market上无法下载软件,总停留在 downloading… 界面,有时候恢复出厂设置又可以下载了,这是为什么呢?

在market上下载一个应用程序发出的URL请求,可以参考Tim Strazzere的博文 Downloading market applications without the vending app

其中涉及到一个参数手机设备号,与Android_ID有关,这个Android_ID可以参看官方文档

http://developer.android.com/reference/android/provider/Settings.Secure.html#ANDROID_ID

“A 64-bit number (as a hex string) that is randomly generated on the device’s first boot and should remain constant for the lifetime of the device. (The value may change if a factory reset is performed on the device.) ”

AndroidId,通常用做识别唯一设备。可以通过下面的函数获取:
String androidId = Settings.System.getString(getContentResolver(),
Settings.Secure.ANDROID_ID);

具体的函数实现与系统环境变量”ro.serialno”有关,大概过程是:

/system/core/init/init.c 读取kernel启动参数 /proc/cmdline 获取 androidboot.serialno, 然后是设置系统变量

property_set(“ro.serialno”, serialno[0] ? serialno : “”);

下面是 HTC G1的kernel启动参数:

~ # cat /proc/cmdline
board_trout.disable_uart3=0 board_trout.usb_h2w_sw=0 board_trout.disable_sdcard=0 smisize=64 androidboot.baseband=2.22.19.26I androidboot.cid=T-MOB010 androidboot.carrier=TMUS androidboot.keycaps=qwerty androidboot.mode=normal androidboot.serialno=HT839GZ04643 androidboot.bootloader=1.33.2005 no_console_suspend=1 console=null
~ # getprop |grep serialno
[ro.serialno]: [HT839GZ04643]

adb shell devices
列出的就是ro.serialno , 如果为空,用默认的值: “1234567890ABCDEF”.
注意,有些机器总是显示 9774d56d682e549c,说明系统环境变量”ro.serialno”没有设置,最近的Android 2.2代码已经修改这个bug.

strazzere的博文提到,可以用 tcpdump与wireshark工具观察数据包,可以得到手机的deviceId。其实也不需要那么麻烦,Android Market依赖于Downloads这个provider,那么查看sqlite数据库也是一目了然的:

$ adb pull /data/data/com.android.providers.downloads/databases/downloads.db .
$ strings downloads.db

或者可以这样找

$ adb pull /data/data/com.android.providers.settings/databases/settings.db .
$ sqlite3 settings.db
$ .tables
$ select * from secure

据观察,deviceId跟ANDROID_ID有一定的关系,deviceId是Google服务器自动生成的。 在Android 2.2之前的版本,可以为伪造一个地址蒙蔽过关,SDK上提供的是16个0。而在android 2.2之后的market,这个值与email帐号挂钩起来了,如果不一致,会收到HTTP 400 的错误码,这样只好把手机恢复出厂设置,重新生成deviceId。

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 2.2 market显示热门付费应用

有 root 权限的,直接编辑

/data/data/com.android.vending/shared_prefs/vending_preferences.xml 

<boolean name="metadata_paid_apps_enabled" value="false" />

修改为:

<boolean name="metadata_paid_apps_enabled" value="true" />

重启机器,即可。

也可以安装 MarketEnable或MarketAccess。

主页面显示:”Android 电子市场没有符合请求的内容“, 解决方法请参考 Android market FAQ 最末尾那段,只能暂时的通过setpro 设置一些属性,重启就失效了。

参考: http://forum.cyanogenmod.com/topic/1972-paid-apps-tab-missing/

Pages: Prev 1 2 3 Next