[QUESTION]
遇到客户要求在gms项目中预设紧急联系人,但是电话簿和电话应用都替换为谷歌应用,无法通过在应用中直接修改,那么只能用其他方式去实现。
[RESOLVENT]
方案一:
查看应用后,发现系统会在开机后创建电话簿数据库,即contacts2.db,那么就可以通过替换该数据库来达到目的(没办法,权限高就是这么任性=_=!)。
1、编译打包:
将数据库文件放到编译目录中,在编译的时候保证数据库会编译并打包到img文件中,如下
device/mediatek/vendor/common/device.mk
ifeq ($(strip $(REPLACE_CONTACTS_DB)), yes)
PRODUCT_COPY_FILES += $(LOCAL_PATH)/contacts2.db:$/system/etc/init/contacts2.db
endif
2、开机后将数据库拷贝到指定目录下,即/data/data/com.android.providers.contacts/databases/下,该路径下的数据库则为联系人的数据库:
/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -9284,6 +9286,26 @@ public class PackageManagerService extends IPackageManager.Stub
public void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags,
long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
+ if (scanDir.getAbsolutePath().contains("data")) {
+ //遍历文件找到应用的/data/目录
+ SharedPreferences mSp =mContext.getSharedPreferences("Copy_Contacts",Context.MODE_PRIVATE);
+ boolean mFirstBoot = mSp.getBoolean(FIRST_BOOT, true);
+ SharedPreferences.Editor editor = mSp.edit();
+ if(mFirstBoot){
+ new Thread(() ->
+ {
+ try {
+ if (new File("/system/etc/init/contacts2.db").exists()) {
+ copyFile("/system/etc/init/contacts2.db", "/data/data/com.android.providers.contacts/databases/contacts2.db");
+ editor.putBoolean(FIRST_BOOT, false); //赋值false,拷贝成功后标记
+ editor.commit();
+ }
+ Thread.sleep(3000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ });
+ }
+ }
try {
scanDirLI(scanDir, parseFlags, scanFlags, currentTime, packageParser, executorService);
} finally {
@@ -9291,6 +9313,37 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
+
+ public void copyFile(String oldPath, String newPath) {
+ try {
+ int bytesum = 0;
+ int byteread = 0;
+ File oldfile = new File(oldPath);
+ File newfile = new File(newPath);
+ if (oldfile.exists() && newfile.exists() ) { //文件存在时
+ InputStream inStream = new FileInputStream(oldPath); //读入原文件
+ FileOutputStream fs = new FileOutputStream(newPath);
+ byte[] buffer = new byte[1444];
+ int length;
+ while ( (byteread = inStream.read(buffer)) != -1) {
+ bytesum += byteread; //字节数 文件大小
+ System.out.println(bytesum);
+ fs.write(buffer, 0, byteread);
+ }
+ inStream.close();
+ Log.d("seven", "PackageManagerService.java-->>copyFile: 复制完成!");
+ }
+ else
+ {
+ Log.d("seven", "PackageManagerService.java-->>copyFile: 文件已存在!");
+ }
+ }
+ catch (Exception e) {
+ Log.d("seven", "PackageManagerService.java-->>copyFile: 文件复制失败!");
+ e.printStackTrace();
+
+ }
+ }
方案二:
该方案通过监听开机广播,启动服务,再调用数据库创建数据的方式来达到预置联系人的目的。
1、监听开机广播&&启动服务
public class BootCmpReceiver extends BroadcastReceiver {
private static final String TAG = "seven";
private static final int ERROR_SUB_ID = -1000;
private static final String FIRST_BOOT = "first_boot";
private SharedPreferences mSp;
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
mSp = context.getSharedPreferences("SimProcessor",Context.MODE_PRIVATE);
// add for multi-user ALPS01964765, whether the current user is running.
// if not , will do nothing.
UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
boolean isRunning = userManager.isUserRunning(new UserHandle(UserHandle.myUserId()));
Log.d(TAG, "[onReceive] action=" + action + " isRunning: " + isRunning
+ "isUserUnlocked() = " + userManager.isUserUnlocked());
if (!isRunning) {
return;
}
/// M: Not support SIM Contacts in guest mode.
if (UserHandle.myUserId() != UserHandle.USER_OWNER) {
Log.i(TAG, "[onReceive], The current user isn't owner !");
return;
}
if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
if (!isPhbReady()) {
processBootComplete(context);
} else {
processDupSimContacts(context);
}
android.util.Log.d("seven", "BootCmpReceiver.java-->>onReceive: 1111111111111111");
//add by fenghuan for import contact
if(context.getResources().getBoolean(R.bool.hct_preload_contacts)) {
boolean mFirstBoot = mSp.getBoolean(FIRST_BOOT, true);
SharedPreferences.Editor editor = mSp.edit();
if(mFirstBoot){
presetServiceNumber(context);
}
editor.putBoolean(FIRST_BOOT, false); //赋值false,表示下次开机不用再拷贝了
editor.commit();
}
//end
} else if (TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED.equals(action)) {
processMultiSimConfigChanged(context);
}
}
private void startSimService(Context context, int subId, int workType) {
Intent intent = null;
intent = new Intent(context, SimProcessorService.class);
intent.putExtra(SimServiceUtils.SERVICE_SUBSCRIPTION_KEY, subId);
intent.putExtra(SimServiceUtils.SERVICE_WORK_TYPE, workType);
Log.d(TAG, "[startSimService]subId:" + subId + "|workType:" + workType);
SimProcessorService.setServiceIsStarting(true);
context.startService(intent);
}
}
2、创建数据
public class SimProcessorService extends Service {
private final static String TAG = "SimProcessorService";
private SimProcessorManager mProcessorManager;
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "[onCreate]...");
GlobalEnv.setApplicationContext(getApplicationContext());
mProcessorManager = new SimProcessorManager(this, mListener);
}
@Override
public int onStartCommand(Intent intent, int flags, int id) {
super.onStartCommand(intent, flags, id);
processIntent(intent);
IS_SERVICE_STARTING = false;
return START_REDELIVER_INTENT;
}
private void processIntent(Intent intent) {
if (intent == null) {
Log.w(TAG, "[processIntent] intent is null.");
return;
}
int subId = intent.getIntExtra(SimServiceUtils.SERVICE_SUBSCRIPTION_KEY, 0);
int workType = intent.getIntExtra(SimServiceUtils.SERVICE_WORK_TYPE, -1);
mProcessorManager.handleProcessor(getApplicationContext(), subId, workType, intent);
}
private SimProcessorManager.ProcessorManagerListener mListener =
new ProcessorManagerListener() {
@Override
public void addProcessor(long scheduleTime, ProcessorBase processor) {
if (processor != null) {
try {
mExecutorService.execute(processor);
Settings.System.putString(getContentResolver(), IMPORT_REMOVE_RUNNING, "true");
sIsSimProcessorRunning = true;
} catch (RejectedExecutionException e) {
Log.e(TAG, "[addProcessor] RejectedExecutionException: " + e.toString());
}
}
}
}
public class PresetContactsImportProcessor extends SimProcessorBase {
private static final String TAG = "PresetContactsImportProcessor";
private static boolean sIsRunningNumberCheck = false;
private static final int INSERT_PRESET_NUMBER_COUNT = 2; //预置联系人的个数
private String[] INSERT_PRESET_NAME ={"110","118","119"}; //= {R.string.preloader_contacts_name1, R.string.preloader_contacts_name2}; //各预置联系人的姓名
private String[] INSERT_PRESET_NUMBER ={"110","118","119"};// = {R.string.preloader_contacts_name1, R.string.preloader_contacts_name1}; //各预置联系人的号码
private int mSubId;
private Context mContext;
public PresetContactsImportProcessor(Context context, int subId, Intent intent, ProcessorCompleteListener listener) {
super(intent, listener);
mContext = context;
mSubId = subId;
INSERT_PRESET_NAME = mContext.getResources().getStringArray(R.array.preloader_contacts_names);
INSERT_PRESET_NUMBER = mContext.getResources().getStringArray(R.array.preloader_contacts_nums);
}
@Override
public int getType() {
return SimServiceUtils.SERVICE_WORK_IMPORT_PRESET_CONTACTS;
}
@Override
public void doWork() {
if (isCancelled()) {
Log.d(TAG, "[doWork]cancel import preset contacts work. Thread id=" + Thread.currentThread().getId());
return;
}
importDefaultReadonlyContact();
}
private void importDefaultReadonlyContact(){
Log.i(TAG, "isRunningNumberCheck before: " + sIsRunningNumberCheck);
if (sIsRunningNumberCheck) {
return;
}
sIsRunningNumberCheck = true;
for(int i = 0;i < INSERT_PRESET_NUMBER.length; i++) {
Log.i(TAG, "isRunningNumberCheck after: " + sIsRunningNumberCheck);
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(INSERT_PRESET_NUMBER[i]));
Log.i(TAG, "getContactInfoByPhoneNumbers(), uri = " + uri);
Cursor contactCursor = mContext.getContentResolver().query(uri,
new String[] {PhoneLookup.DISPLAY_NAME, PhoneLookup.PHOTO_ID}, null, null, null);
try {
if (contactCursor != null && contactCursor.getCount() > 0) {
return;
} else {
final ArrayList operationList = new ArrayList();
ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI);
ContentValues contactvalues = new ContentValues();
contactvalues.put(RawContacts.ACCOUNT_NAME, "Phone");
contactvalues.put(RawContacts.ACCOUNT_TYPE, "Local Phone Account");
contactvalues.put(MtkContactsContract.RawContactsColumns.INDICATE_PHONE_SIM, MtkContactsContract.RawContacts.INDICATE_PHONE);
builder.withValues(contactvalues);
builder.withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DISABLED);
operationList.add(builder.build());
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
builder.withValue(Phone.TYPE, Phone.TYPE_MOBILE);
builder.withValue(Phone.NUMBER, INSERT_PRESET_NUMBER[i]);
operationList.add(builder.build());
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
builder.withValue(StructuredName.DISPLAY_NAME, INSERT_PRESET_NAME[i]);
builder.withValue(Data.IS_PRIMARY, 1);
operationList.add(builder.build());
try {
mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList);
} catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
}
} finally {
// when this service start,but the contactsprovider has not been started yet.
// the contactCursor perhaps null, but not always.(first load will weekup the provider)
// so add null block to avoid nullpointerexception
if (contactCursor != null) {
contactCursor.close();
}
}
Log.i(TAG, "isRunningNumberCheck insert: " + sIsRunningNumberCheck);
sIsRunningNumberCheck = false;
}
}
}
小结:
方法一只做参考,该方法弊端太大了,且不容易维护,在复制文件时容易出现问题,但是还是有可取之处的,预置文件是可以用该方法的,不过需要完善;
方法二除了用来预置联系人,还可以用来执行开机以后需要完成的任务,且可维护性强,后续有需求可完善该方式。