\packages\apps\PackageInstaller
一、一条真实的修改记录
TVOS基于的是一套板卡厂商原有的源码(mstar android8.0版本)原生的这个app安装界面,存在俩个比较严重的用户体验问题,
1’、下面那俩按钮太小了,而且都在右下角干嘛,这么大的屏幕往中间来啊
2、每次安装的时候遥控器的焦点不在安装按钮和完成按钮上,而是在权限列表上,用户还要自己点下去
很好解决,找到布局然后修改布局就可,
install_installing
install_staging
install_failed
install_confirm_perm
install_confirm_perm_update
install_success
install_confirm
然后我就把按钮改大一点,这几个布局都要改哦
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout android:id="@+id/app_snippet"
android:background="?android:attr/colorPrimary"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
android:orientation="horizontal"
android:elevation="@dimen/headerElevation"
android:gravity="center_vertical">
<ImageView
android:id="@+id/app_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:scaleType="fitCenter" />
<TextView
android:id="@+id/app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?android:attr/titleTextStyle" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="16dip"
android:paddingRight="16dip">
<ImageView
android:layout_width="92dp"
android:layout_height="92dp"
android:layout_marginBottom="12dp"
android:contentDescription="@null"
android:tint="@color/bigIconColor"
android:src="@drawable/ic_file_download" />
<ProgressBar
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:indeterminate="false" />
<TextView
android:id="@+id/center_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="@string/installing"
android:textAppearance="?android:attr/textAppearanceMedium" />
</LinearLayout>
<LinearLayout
android:id="@+id/buttons_panel"
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:measureWithLargestChild="true"
android:orientation="horizontal"
android:gravity="center"
android:padding="8dip">
<!-- <View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />-->
<Button
android:id="@+id/cancel_button"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="320dp"
android:layout_height="wrap_content"
android:maxLines="2"
android:text="@string/cancel" />
</LinearLayout>
</LinearLayout>
\packages\apps\PackageInstaller\src\com\android\packageinstaller\PackageInstallerActivity.java
让他每次进入都获取焦点,完事
二、然后呢!不妨一起来看一下PackageInstaller
调用安装界面
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(xx, "application/vnd.android.package-archive");
没错,就是type--》"application/vnd.android.package-archive"
packages\apps\PackageInstaller\src\com\android\packageinstaller\InstallStart
Intent nextActivity = new Intent(intent);
nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
// The the installation source as the nextActivity thinks this activity is the source, hence
// set the originating UID and sourceInfo explicitly
nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);
nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);
nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);
if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Uri packageUri = intent.getData();
if (packageUri == null) {
//uri是空的就退出了
// if there's nothing to do, quietly slip into the ether
Intent result = new Intent();
result.putExtra(Intent.EXTRA_INSTALL_RESULT,
PackageManager.INSTALL_FAILED_INVALID_URI);
setResult(RESULT_FIRST_USER, result);
nextActivity = null;
} else {
//判断Uri的Scheme,如果是就跳转到InstallStaging
if (packageUri.getScheme().equals(SCHEME_CONTENT)) {
nextActivity.setClass(this, InstallStaging.class);
} else {
nextActivity.setClass(this, PackageInstallerActivity.class);
}
}
}
if (nextActivity != null) {
startActivity(nextActivity);
}
finish();
跳转根据uri的类型跳转吧,InstallStaging与PackageInstallerActivity
7.0及以后的系统,file://ur就会报FileUriException异常,所以需要使用FileContentProvider来将file://uri替换成content://uri,
packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))就是这样的一个判断条件,7.0以上走InstallStaging。
看看InstallStaging ,来看下onresume
@Override
protected void onResume() {
super.onResume();
// This is the first onResume in a single life of the activity
if (mStagingTask == null) {
// File does not exist, or became invalid
if (mStagedFile == null) {
// Create file delayed to be able to show error
try {
mStagedFile = TemporaryFileManager.getStagedFile(this);
} catch (IOException e) {
showError();
return;
}
}
mStagingTask = new StagingAsyncTask();
mStagingTask.execute(getIntent().getData());
}
}
哦吼,你会发现起了个stagintask,把uri直接传进去了
private final class StagingAsyncTask extends AsyncTask<Uri, Void, Boolean> {
@Override
protected Boolean doInBackground(Uri... params) {
//取,存-----缓存安装包文件----到mStagedFile
if (params == null || params.length <= 0) {
return false;
}
Uri packageUri = params[0];
try (InputStream in = getContentResolver().openInputStream(packageUri)) {
// Despite the comments in ContentResolver#openInputStream the returned stream can
// be null.
if (in == null) {
return false;
}
try (OutputStream out = new FileOutputStream(mStagedFile)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = in.read(buffer)) >= 0) {
// Be nice and respond to a cancellation
if (isCancelled()) {
return false;
}
out.write(buffer, 0, bytesRead);
}
}
} catch (IOException | SecurityException e) {
Log.w(LOG_TAG, "Error staging apk from content URI", e);
return false;
}
return true;
}
@Override
protected void onPostExecute(Boolean success) {
if (success) {
// Now start the installation again from a file
//哦吼,又跳转了!!!!!!!还是PackageInstallerActivity
Intent installIntent = new Intent(getIntent());
installIntent.setClass(InstallStaging.this, PackageInstallerActivity.class);
installIntent.setData(Uri.fromFile(mStagedFile));
installIntent
.setFlags(installIntent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT);
installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivityForResult(installIntent, 0);
} else {
showError();
}
}
}
将content://uri 转成了file://uri,又跳转了PackageInstallerActivity
PackageInstallerActivity oncrate
bindUi(R.layout.install_confirm, false);
checkIfAllowedAndInitiateInstall();
checkIfAllowedAndInitiateInstall检查是不是已经允许了安装位置源应用
然后initiateInstall
然后startInstallConfirm
private void startInstallConfirm() {
// We might need to show permissions, load layout with permissions
if (mAppInfo != null) {
//加载app更新布局
bindUi(R.layout.install_confirm_perm_update, true);
} else {
//加载确认安装布局
bindUi(R.layout.install_confirm_perm, true);
}
((TextView) findViewById(R.id.install_confirm_question))
.setText(R.string.install_confirm_question);
TabHost tabHost = (TabHost)findViewById(android.R.id.tabhost);
tabHost.setup();
ViewPager viewPager = (ViewPager)findViewById(R.id.pager);
TabsAdapter adapter = new TabsAdapter(this, tabHost, viewPager);
// If the app supports runtime permissions the new permissions will
// be requested at runtime, hence we do not show them at install.
boolean supportsRuntimePermissions = mPkgInfo.applicationInfo.targetSdkVersion
>= Build.VERSION_CODES.M;
boolean permVisible = false;
mScrollView = null;
mOkCanInstall = false;
int msg = 0;
//加载需要的权限,显示到布局中mScrollView
AppSecurityPermissions perms = new AppSecurityPermissions(this, mPkgInfo);
final int N = perms.getPermissionCount(AppSecurityPermissions.WHICH_ALL);
if (mAppInfo != null) {
msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
? R.string.install_confirm_question_update_system
: R.string.install_confirm_question_update;
mScrollView = new CaffeinatedScrollView(this);
mScrollView.setFillViewport(true);
boolean newPermissionsFound = false;
if (!supportsRuntimePermissions) {
newPermissionsFound =
(perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0);
if (newPermissionsFound) {
permVisible = true;
mScrollView.addView(perms.getPermissionsView(
AppSecurityPermissions.WHICH_NEW));
}
}
if (!supportsRuntimePermissions && !newPermissionsFound) {
LayoutInflater inflater = (LayoutInflater)getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
TextView label = (TextView)inflater.inflate(R.layout.label, null);
label.setText(R.string.no_new_perms);
mScrollView.addView(label);
}
adapter.addTab(tabHost.newTabSpec(TAB_ID_NEW).setIndicator(
getText(R.string.newPerms)), mScrollView);
}
if (!supportsRuntimePermissions && N > 0) {
permVisible = true;
LayoutInflater inflater = (LayoutInflater)getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
View root = inflater.inflate(R.layout.permissions_list, null);
if (mScrollView == null) {
mScrollView = (CaffeinatedScrollView)root.findViewById(R.id.scrollview);
}
((ViewGroup)root.findViewById(R.id.permission_list)).addView(
perms.getPermissionsView(AppSecurityPermissions.WHICH_ALL));
adapter.addTab(tabHost.newTabSpec(TAB_ID_ALL).setIndicator(
getText(R.string.allPerms)), root);
}
if (!permVisible) {
if (mAppInfo != null) {
// This is an update to an application, but there are no
// permissions at all.
msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
? R.string.install_confirm_question_update_system_no_perms
: R.string.install_confirm_question_update_no_perms;
} else {
// This is a new application with no permissions.
msg = R.string.install_confirm_question_no_perms;
}
// We do not need to show any permissions, load layout without permissions
bindUi(R.layout.install_confirm, true);
mScrollView = null;
}
if (msg != 0) {
((TextView)findViewById(R.id.install_confirm_question)).setText(msg);
}
if (mScrollView == null) {
// There is nothing to scroll view, so the ok button is immediately
// set to install.
mOk.setText(R.string.install);
mOkCanInstall = true;
} else {
mScrollView.setFullScrollAction(new Runnable() {
@Override
public void run() {
mOk.setText(R.string.install);
mOkCanInstall = true;
}
});
}
}
public void onClick(View v) {
if (v == mOk) {
//ok被点击,开始安装
if (mOk.isEnabled()) {
if (mOkCanInstall || mScrollView == null) {
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, true);
finish();
} else {
startInstall();
}
} else {
mScrollView.pageScroll(View.FOCUS_DOWN);
}
}
} else if (v == mCancel) {
// Cancel and finish
setResult(RESULT_CANCELED);
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, false);
}
finish();
}
}
startInstall();跳转到InstallInstalling
InstallInstalling onresunme启动InstallingAsyncTask
InstallingAsyncTask的doInBackground方法中会根据包的Uri,将APK的信息通过IO流的形式写入到PackageInstaller.Session中
/**
* Send the package to the package installer and then register a event result observer that
* will call {@link #launchFinishBasedOnResult(int, int, String)}
*/
private final class InstallingAsyncTask extends AsyncTask<Void, Void,
PackageInstaller.Session> {
volatile boolean isDone;
@Override
protected PackageInstaller.Session doInBackground(Void... params) {
PackageInstaller.Session session;
try {
session = getPackageManager().getPackageInstaller().openSession(mSessionId);
} catch (IOException e) {
return null;
}
session.setStagingProgress(0);
try {
File file = new File(mPackageURI.getPath());
try (InputStream in = new FileInputStream(file)) {
long sizeBytes = file.length();
try (OutputStream out = session
.openWrite("PackageInstaller", 0, sizeBytes)) {
byte[] buffer = new byte[4096];
while (true) {
int numRead = in.read(buffer);
if (numRead == -1) {
session.fsync(out);
break;
}
if (isCancelled()) {
session.close();
break;
}
out.write(buffer, 0, numRead);
if (sizeBytes > 0) {
float fraction = ((float) numRead / (float) sizeBytes);
session.addProgress(fraction);
}
}
}
}
return session;
} catch (IOException | SecurityException e) {
Log.e(LOG_TAG, "Could not write package", e);
session.close();
return null;
} finally {
synchronized (this) {
isDone = true;
notifyAll();
}
}
}
@Override
protected void onPostExecute(PackageInstaller.Session session) {
if (session != null) {
Intent broadcastIntent = new Intent(BROADCAST_ACTION);
broadcastIntent.setPackage(
getPackageManager().getPermissionControllerPackageName());
broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
InstallInstalling.this,
mInstallId,
broadcastIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
session.commit(pendingIntent.getIntentSender());
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
} else {
getPackageManager().getPackageInstaller().abandonSession(mSessionId);
if (!isCancelled()) {
launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null);
}
}
}
}
这里出现了一个 session = getPackageManager().getPackageInstaller().openSession(mSessionId);调用PackageInstaller.Session的commit方法,将APK的信息交由PMS处理。
也就是说安装的逻辑就跑到PM那里去咯 frameworks/base/core/java/android/content/pm/PackageInstaller.java
然后呢结果通过InstallEventReceiver监听安装结果,这样基本上就完成了。所以PackageInstaller想要知道是如何安装的,还要去看pm。