添加WidgetView到自己的应用

添加指定应用的WidgetView到自己的应用

(一)基本方案

话不多说,直接上最常规的方法。

package com.demo.widget;

import static android.util.Log.d;
import android.app.Activity;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnLongClickListener;
import android.widget.RelativeLayout;
import android.widget.RelativeLayout.LayoutParams;
import android.widget.Toast;

public class Main extends Activity {
    private AppWidgetHost mAppWidgetHost;
    private AppWidgetManager mAppWidgetManager;
    private RelativeLayout layout;  

    private static final int REQUEST_PICK_APPWIDGET = 1;
    private static final int REQUEST_CREATE_APPWIDGET = 2;  
    private static final int REQUEST_BIND_APPWIDGET = 3;    
    private static final int APPWIDGET_HOST_ID = 0x100;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mAppWidgetManager = AppWidgetManager.getInstance(getApplicationContext());
        mAppWidgetHost = new AppWidgetHost(getApplicationContext(), APPWIDGET_HOST_ID);
        //开始监听widget的变化
        mAppWidgetHost.startListening();

        layout = new RelativeLayout(this);
        LayoutParams p = new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
        layout.setBackgroundColor(Color.MAGENTA);
        layout.setLayoutParams(p);
        //长按屏幕添加Widget到当前activity中
        layout.setOnLongClickListener(new OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                addWidget();
                return false;
            }
        });
        setContentView(layout);
    }

    private void addWidget() {
        int appWidgetId = mAppWidgetHost.allocateAppWidgetId();

        /**
         * 方式一:
         * 启动系统“设置”应用的 AppWidgetPickActivity,会弹出已经注册了WidgetProvider的Widget列表对话框,选择我们要添加的Widget就ok
         */
        Intent pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK);
        pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
        startActivityForResultSafely(pickIntent, REQUEST_PICK_APPWIDGET);

        /**
         * 方式二:
         * 启动系统“设置”应用的AllowBindAppWidgetActivity,会弹出一个确认权限的对话框,点击确定,就可以添加我们需要的Widget了
         * 只能添加我们指定的某个应用的Widget
         */
        ComponentName provider = new ComponentName("com.autonavi.amapauto", "com.autonavi.autowidget.AutoWidgetProvider");
        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, provider);
        // 有options配置的,添加options
        // intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
        startActivityForResultSafely(intent, REQUEST_BIND_APPWIDGET);
    }        

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        d("onActivityResult", "resultCode:"+ resultCode);
        if (resultCode == RESULT_OK) {
            switch (requestCode) {
            case REQUEST_PICK_APPWIDGET:
                addAppWidget(data);
                break;
            case REQUEST_BIND_APPWIDGET:
                completeAddAppWidget(data);
                break;
            case REQUEST_CREATE_APPWIDGET:
                completeAddAppWidget(data);
                break;
            }
        } else if (requestCode == REQUEST_PICK_APPWIDGET && resultCode == RESULT_CANCELED && data != null) {
            // Clean up the appWidgetId if we canceled
            int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
            if (appWidgetId != -1) {
                mAppWidgetHost.deleteAppWidgetId(appWidgetId);
            }
        }
    }

    /**
     * 选中了某个widget之后,根据是否有配置来决定直接添加还是弹出配置activity
     * @param data
     */
    private void addAppWidget(Intent data) {
        int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);

        String customWidget = data.getStringExtra(EXTRA_CUSTOM_WIDGET);
        d("addAppWidget", "appWidgetId:"+ appWidgetId);
        AppWidgetProviderInfo appWidget = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
        d("addAppWidget", "configure:"+ appWidget.configure);
        if (appWidget.configure != null) {
            //有配置,弹出配置
            Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
            intent.setComponent(appWidget.configure);
            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
             startActivityForResult(intent, REQUEST_CREATE_APPWIDGET);
         } else {
            //没有配置,直接添加
             onActivityResult(REQUEST_CREATE_APPWIDGET, Activity.RESULT_OK, data);
         }
    }

    /**
     * 添加widget到我们的activity
     * @param data
     */
    private void completeAddAppWidget(Intent data) {
        Bundle extras = data.getExtras();
        int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);

        d("completeAddAppWidget", "dumping extras content="+extras.toString());
        d("completeAddAppWidget", "appWidgetId:"+ appWidgetId);
        AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);

        View hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
        LayoutParams p = new LayoutParams(300,300);
        p.addRule(RelativeLayout.CENTER_IN_PARENT, 1);
        layout.addView(hostView, p);        
    }

    private void startActivityForResultSafely(Intent intent, int requestCode) {
        try {
            startActivityForResult(intent, requestCode);
        } catch (ActivityNotFoundException e) {
            Toast.makeText(this, e.toString(), Toast.LENGTH_SHORT).show();
        } catch (SecurityException e) {
            Toast.makeText(this, e.toString(), Toast.LENGTH_SHORT).show();
            Log.e("startActivityForResult", "Launcher does not have the permission to launch " + intent +
                    ". Make sure to create a MAIN intent-filter for the corresponding activity " +
                    "or use the exported attribute for this activity.", e);
        }
    }
}

  常规方法中,必须要使用到系统的设置,方式一中,使用Intent启动设置应用中的AppwidgetPickActivity,弹出当前系统中可用的Widget,选择其中某一个,然后回调ActivityOnResult()方法。方式二中,使用Intent启动的是设置应用中的另一个Activity,即AllowBindAppWidgetActivity,弹出需要确认权限的对话框,然而要注意到的是,这个Intent要包含具体的AppWidget_ID和AppWidget_Provider,也就是,此种方式是添加某个特定的Widget。确认对话框后,还是会回调ActivityOnResult()方法。在ActivityOnResult()的回调中,返回的Intent中包含已经初始化绑定的widget的id,通过这个ID,即可得到已经绑定成功的AppWidgetProviderInfo,然后使用AppWidgetHost.createView(……),就最终得到我们需要的widget的view。

(二)复杂方案

  上述的基础方案,是可以成功地取出我们需要的Widget的view,并加载在我们自己的应用中,但是弹出的对话框是不可去除的一个步骤,那这就可能不符合我们的业务需求了。在实际需求开发中,需要的是在启动应用后,直接将某一个特定的Widget,整合到应用中,中间不能有多余的屏幕互动,那这样一来,第一种方案就满足不了需求了。
  但是我们可以发现,第一种的方案中,是在点击对话框之后才正式初始化绑定AppWidgetID,那么到底在系统设置应用中的AllowBindAppWidgetActivity中,发生了那些故事呢?这个类在设置 Settings源码中com.android.settings包下,下面直接贴上源码:

    /*
 * Copyright (C) 2012 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.
 */

package com.android.settings;

import android.app.AlertDialog;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.widget.CheckBox;

import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;

/**
 * This activity is displayed when an app launches the BIND_APPWIDGET intent. This allows apps
 * that don't have the BIND_APPWIDGET permission to bind specific widgets.
 */
public class AllowBindAppWidgetActivity extends AlertActivity implements
        DialogInterface.OnClickListener {

    private CheckBox mAlwaysUse;
    private int mAppWidgetId;
    private ComponentName mComponentName;
    private String mCallingPackage;
    private AppWidgetManager mAppWidgetManager;

    // Indicates whether this activity was closed because of a click
    private boolean mClicked;

    public void onClick(DialogInterface dialog, int which) {
        if (which == AlertDialog.BUTTON_POSITIVE) {
            // By default, set the result to cancelled
            setResult(RESULT_CANCELED);
            if (mAppWidgetId != -1 && mComponentName != null && mCallingPackage != null) {
                try {
                    mAppWidgetManager.bindAppWidgetId(mAppWidgetId, mComponentName);
                    Intent result = new Intent();
                    result.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
                    setResult(RESULT_OK, result);
                } catch (Exception e) {
                    Log.v("BIND_APPWIDGET", "Error binding widget with id "
                            + mAppWidgetId + " and component " + mComponentName);
                }
            }
            boolean alwaysAllowBind = mAlwaysUse.isChecked();
            if (alwaysAllowBind != mAppWidgetManager.hasBindAppWidgetPermission(mCallingPackage)) {
                mAppWidgetManager.setBindAppWidgetPermission(mCallingPackage, alwaysAllowBind);
            }
        }
        finish();
    }

    protected void onDestroy() {
        if (!mClicked) {
            setResult(RESULT_CANCELED);
            finish();
        }
        super.onDestroy();
    }

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent intent = getIntent();
        CharSequence label = "";
        if (intent != null) {
            try {
                mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
                mComponentName = (ComponentName)
                        intent.getParcelableExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER);
                mCallingPackage = getCallingPackage();
                PackageManager pm = getPackageManager();
                ApplicationInfo ai = pm.getApplicationInfo(mCallingPackage, 0);
                label = pm.getApplicationLabel(ai);
            } catch (Exception e) {
                mAppWidgetId = -1;
                mComponentName = null;
                mCallingPackage = null;
                Log.v("BIND_APPWIDGET", "Error getting parameters");
                setResult(RESULT_CANCELED);
                finish();
                return;
            }
        }
        AlertController.AlertParams ap = mAlertParams;
        ap.mTitle = getString(R.string.allow_bind_app_widget_activity_allow_bind_title);
        ap.mMessage = getString(R.string.allow_bind_app_widget_activity_allow_bind, label);
        ap.mPositiveButtonText = getString(R.string.create);
        ap.mNegativeButtonText = getString(android.R.string.cancel);
        ap.mPositiveButtonListener = this;
        ap.mNegativeButtonListener = this;
        LayoutInflater inflater =
                (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
        mAlwaysUse = (CheckBox) ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
        mAlwaysUse.setText(getString(R.string.allow_bind_app_widget_activity_always_allow_bind, label));

        mAlwaysUse.setPadding(mAlwaysUse.getPaddingLeft(),
                mAlwaysUse.getPaddingTop(),
                mAlwaysUse.getPaddingRight(),
                (int) (mAlwaysUse.getPaddingBottom() +
                        getResources().getDimension(R.dimen.bind_app_widget_dialog_checkbox_bottom_padding)));

        mAppWidgetManager = AppWidgetManager.getInstance(this);
        mAlwaysUse.setChecked(mAppWidgetManager.hasBindAppWidgetPermission(mCallingPackage));

        setupAlert();
    }
}

  直奔目的,我们看到对话框的确认按钮点击事件中,最为关键的一步,mAppWidgetManager.bindAppWidgetId(mAppWidgetId, mComponentName),这一步是初始化绑定AppWidgetID最为核心的操作,关于AppWidget绑定实现机制的深入分析,可以参考大牛的文章AppWidgetService的代理机制。接着下面还有一个设置绑定权限的操作mAppWidgetManager.setBindAppWidgetPermission(mCallingPackage, alwaysAllowBind)
  ok,看到这,那我们直接在自己的应用中实现绑定的初始化操作不就ok了么,为啥还要动用设置里面的内容?尝试过的小伙伴可能就会发现了,这样直接调用AppWidgetManager.bindAppWidgetId()或者AppWidgetManager.bindAppWidgetIdIfAllowed(),会发现绑定不成功,原因是没有进行绑定的权限。
  其实当第一次添加widget的时候都会走到这里,检查该应用是否在系统白名单中,如果是的,则直接bind成功。那么绑定需要哪些权限呢?其实只需要一条权限,android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS,但是这个权限是系统级别的,所以需要我们的应用作为系统级应用,在manifest文件中添加android:sharedUserId=”android.uid.system”,而且还需要在Android.mk里 把LOCAL_CERTIFICATE := shared 改为LOCAL_CERTIFICATE := platform

<manifest  
    xmlns:android="http://schemas.android.com/apk/res/android"  
    package="com.android.app"  
    android:sharedUserId="android.uid.system" >  
    <uses-sdk android:targetSdkVersion="21" android:minSdkVersion="16"/>  

    <uses-permission android:name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/>  

</manifest>  
# Copyright 2007-2008 The Android Open Source Project

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := optional

LOCAL_CERTIFICATE := platform
... ...

  当我们的应用像设置应用一样,获取到系统级绑定的权限后,就可以直接在我们自己的应用中绑定Widget了:

package com.android.app;

import android.app.Activity;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

public class SecondWayActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initNaviWidget();
    }
    /*****************************************************************************************************/
    private static final int APPWIDGET_HOST_ID = 0x125;
    private int mAppWidgetId;
    static final String NAVI_PACKAGE_NAME = "com.xxx.xxx";
    static final String NAVI_CLASS_NAME = "com.xxx.xxx.xxxWidgetProvider";
    private final static String SP_NAME="widget_demo_sp";
    private final static String KEY_APPWIDGET_ID="widget_id";
    private AppWidgetHost mAppWidgetHost;
    private AppWidgetManager mAppWidgetManager;
    private View mNaviWidgetView;

    private void initNaviWidget() {
        mAppWidgetManager = AppWidgetManager
                .getInstance(getApplicationContext());
        mAppWidgetHost = new AppWidgetHost(getApplicationContext(),
                APPWIDGET_HOST_ID);
        // 开始监听widget的变化
        mAppWidgetHost.startListening();

        SharedPreferences sp = this.getSharedPreferences(SP_NAME, 0);
        int appWidgetId=sp.getInt(KEY_APPWIDGET_ID, -1);
        if(appWidgetId<=0){
            createAppWidgetId();
        }else{
            mAppWidgetId = appWidgetId;
        }

        loadNaviWidget();
    }

    private void createAppWidgetId(){
        int appWidgetId = mAppWidgetHost.allocateAppWidgetId();
        Log.d("create", "appWidgetId = "+appWidgetId);
        mAppWidgetId = appWidgetId;
        ComponentName provider = new ComponentName(NAVI_PACKAGE_NAME, NAVI_CLASS_NAME);
        try{
            /**
            在API20以下的版本中,是可以直接使用bindAppWidgetId()方法的,而且不需要使用setBindAppWidgetPermission()方法。但是往高的版本,只能调用bindAppWidgetIdIfAllowed,而且因为setBindAppWidgetPermission()成为私有方法,必须使用到反射,才能调用。

            mAppWidgetManager.setBindAppWidgetPermission(getCallingPackage(), false);
            mAppWidgetManager.bindAppWidgetIdIfAllowed(mAppWidgetId, provider);
            */
            mAppWidgetManager.bindAppWidgetId(mAppWidgetId, provider);
            SharedPreferences sp = this.getSharedPreferences(SP_NAME, 0);
            Editor editor=sp.edit();
            editor.putInt(KEY_APPWIDGET_ID, appWidgetId);
            editor.commit();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void loadNaviWidget() {
        Log.e("load", "mAppWidgetId = "+mAppWidgetId);
        AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(mAppWidgetId);
        if(appWidgetInfo==null){
            createAppWidgetId();
            Log.d("load", "mAppWidgetId22 = "+mAppWidgetId);
            appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(mAppWidgetId);
        }
        mNaviWidgetView= mAppWidgetHost.createView(this, mAppWidgetId, appWidgetInfo);
        if (mNaviWidgetView != null) {
            //......要处理的业务
        }
    }

}

  另外,使用生成的APK,再重新签名的办法,也可以让应用成为系统级应用:
1、同上,加入android:sharedUserId=”android.uid.system”这个属性。
2、使用eclipse编译出apk文件,但是这个apk文件是不能用的。
3、用压缩软件打开apk文件,删掉META-INF目录下的CERT.SF和CERT.RSA两个文件。
4、使用目标系统的platform密钥来重新给apk文件签名。这步比较麻烦,首先找到密钥文件,在我的Android源码目录中的位置是”build\target\product\security”,下面的platform.pk8和platform.x509.pem两个文件。然后用Android提供的Signapk工具来签名,signapk的源代码是在”build\tools\signapk”下,用法为”signapk platform.x509.pem platform.pk8 input.apk output.apk”,文件名最好使用绝对路径防止找不到,也可以修改源代码直接使用。(摘录自烟雨随风

  但是不管使用哪种方法,生成的系统级应用,只能在我们自己编译的系统里或者原生Android系统里运行。那如果只是APP的开发,不涉及系统或者framwork的开发,那我们又该寻求另外的解决方法了。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值