1 安装入口PackageInstallerActivity,这个类只是在安装前做准备。通过各种校验,然后弹出被安装应用的权限框,等待用户安装。具体的流程如下

1.1  求mSessionId 如果是已经存在的则判断对应的SessinInfo是否存在,否则默认一个-1

  

 if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
			//可能是系统级别的应用安装时,需要授权走这个流程
            final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
            final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
            if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
                Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
                finish();
                return;
            }
			//如果有SessInfo则证明传过来的sessionId是有效的,并且获取packageUri
            mSessionId = sessionId;
            packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath));
            mOriginatingURI = null;
            mReferrerURI = null;
        } else {
			//如果是用户自己拉起来的安装,则默认sessionId为-1 病且获取 packageUri
            mSessionId = -1;
            packageUri = intent.getData();
            mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
            mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
        }

1.2 开始做校验

	// 返回URI解析错误 -3
        if (packageUri == null) {
            Log.w(TAG, "Unspecified source");
            setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
            finish();
            return;
        }
        //如果是手表就不支持
        if (DeviceUtils.isWear(this)) {
            showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
            return;
        }

1. 3 如果是系统应用拉起安装则直接进入包分析阶段

 final boolean requestFromUnknownSource = isInstallRequestFromUnknownSource(intent);
        if (!requestFromUnknownSource) {
			//进入packageUri处理阶段
            processPackageUri(packageUri);
            return;
        }
		 //安装请求是否来自于一个未知的源。
		private boolean isInstallRequestFromUnknownSource(Intent intent) {
			String callerPackage = getCallingPackage();
			if (callerPackage != null && intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)) {
				try {
					mSourceInfo = mPm.getApplicationInfo(callerPackage, 0);
					if (mSourceInfo != null) {
						if ((mSourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
							// Privileged apps are not considered an unknown source.
							//如果安装请求是来自于一个系统应用,则可以明确源是已知的
							return false;
						}
					}
				} catch (NameNotFoundException e) { }
			}
			return true;//否则源是未知
		}

1. 4 针对管理员账户做不同的处理

	// If the admin prohibits it, or we're running in a managed profile, just show error
        // and exit. Otherwise show an option to take the user to Settings to change the setting.
        // 是否是管理员用户
        final boolean isManagedProfile = mUserManager.isManagedProfile();
        if (isUnknownSourcesDisallowed()) {
            //如果有用户限制了未知来源应用的安装
            if ((mUserManager.getUserRestrictionSource(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
                    Process.myUserHandle()) & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
                //如果不是system用户限制当前用户安装未知来源app,启动设置,使用户在设置里面修改
                showDialogInner(DLG_UNKNOWN_SOURCES);
            } else {
				//如果是system用户限制的,则直接退出
                startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
                clearCachedApkIfNeededAndFinish();
            }
        } else if (!isUnknownSourcesEnabled() && isManagedProfile) {
            //如果不允许安装未知市场的应用,并且当前是管理员用户,则弹出"您的管理员不允许安装来源不明的应用"的对话框
            showDialogInner(DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES);
        } else if (!isUnknownSourcesEnabled()) {
            // Ask user to enable setting first
            //如果不允许安装未知市场的应用,则弹出这个对话框修改设置
            showDialogInner(DLG_UNKNOWN_SOURCES);
        } else {
			//进入packageUri处理阶段
            processPackageUri(packageUri);
        }

1. 5 处理文件的uri

	    //处理包的uri
		private void processPackageUri(final Uri packageUri) {
			mPackageURI = packageUri;
			final String scheme = packageUri.getScheme();
			final PackageUtil.AppSnippet as;
			switch (scheme) {
				case SCHEME_PACKAGE:
					try {
						mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(), PackageManager.GET_PERMISSIONS | PackageManager.GET_UNINSTALLED_PACKAGES);
					} catch (NameNotFoundException e) { }
					if (mPkgInfo == null) {
						Log.w(TAG, "Requested package " + packageUri.getScheme() + " not available. Discontinuing installation");
						showDialogInner(DLG_PACKAGE_ERROR);
						setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
						return;
					}
					as = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo), mPm.getApplicationIcon(mPkgInfo.applicationInfo));
					break;
				case SCHEME_FILE:
					File sourceFile = new File(packageUri.getPath());
					PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);
					// Check for parse errors
					if (parsed == null) {
						Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
						showDialogInner(DLG_PACKAGE_ERROR);
						setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
						return;
					}
					mPkgInfo = PackageParser.generatePackageInfo(parsed, null,  PackageManager.GET_PERMISSIONS, 0, 0, null, new PackageUserState());
					as = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
					break;
				case SCHEME_CONTENT:
					//重新复制一个安装包在解析,返回一个SCHEME_FILE类型的uri重新解析包uri
					mStagingAsynTask = new StagingAsyncTask();
					mStagingAsynTask.execute(packageUri);
					return;
				default:
					Log.w(TAG, "Unsupported scheme " + scheme);
					setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
					clearCachedApkIfNeededAndFinish();
					return;
			}
			PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);
			//启动安装
			initiateInstall();
		}

1.6 启动安装

//启动安装
		private void initiateInstall() {
			String pkgName = mPkgInfo.packageName;
			// Check if there is already a package on the device with this name
			// but it has been renamed to something else. 是否有同名应用已经安装上去了。在此安装则被认为是替换安装
			String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName });
			if (oldName != null && oldName.length > 0 && oldName[0] != null) {
				pkgName = oldName[0];
				mPkgInfo.packageName = pkgName;
				mPkgInfo.applicationInfo.packageName = pkgName;
			}
			// Check if package is already installed. display confirmation dialog if replacing pkg
			// 检查这个包是否真的被安装,如果要替换,则显示替换对话框
			try {
				// This is a little convoluted because we want to get all uninstalled
				// apps, but this may include apps with just data, and if it is just
				// data we still want to count it as "installed".
				// 获取设备上有残存数据,并且标记为“installed”的,实际上已经被卸载的应用。
				mAppInfo = mPm.getApplicationInfo(pkgName, PackageManager.GET_UNINSTALLED_PACKAGES);
				if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
					////如果应用是被卸载的,但是又是被标识成安装过的,则认为是新安装
					mAppInfo = null;
				}
			} catch (NameNotFoundException e) {
				mAppInfo = null;
			}
			//列出权限列表,等待用户确认安装
			startInstallConfirm();
		}

1.7 确认安装权限

private void startInstallConfirm() {
			TabHost tabHost = (TabHost)findViewById(android.R.id.tabhost);
			tabHost.setup();
			tabHost.setVisibility(View.VISIBLE);
			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.
			// 如果app支持运行时权限,这里会显示新的运行时权限
			// 根据版本判断app是否有可能有运行时权限
			boolean supportsRuntimePermissions = mPkgInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M;
			boolean permVisible = false;
			mScrollView = null;
			mOkCanInstall = false;
			int msg = 0;
			//perms这个对象包括了该应用的用户的uid以及相应的一些权限,以及权限组信息。
			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;
						//显示新添加的权限项(这个view竟然是系统的view)
						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);
			} else  {
				findViewById(R.id.tabscontainer).setVisibility(View.GONE);
				findViewById(R.id.spacer).setVisibility(View.VISIBLE);
			}
			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;
					findViewById(R.id.spacer).setVisibility(View.VISIBLE);
				} else {
					// This is a new application with no permissions.
					msg = R.string.install_confirm_question_no_perms;
				}
				tabHost.setVisibility(View.INVISIBLE);
				mScrollView = null;
			}
			if (msg != 0) {
				((TextView)findViewById(R.id.install_confirm_question)).setText(msg);
			}
			mInstallConfirm.setVisibility(View.VISIBLE);
			mOk.setEnabled(true);
			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;
                }
            });
        }
    }

1. 8 点击安装

public void onClick(View v) {
			if (v == mOk) {
				if (mOkCanInstall || mScrollView == null) {
					if (mSessionId != -1) {
						//如果原来是确认权限请求则赋予安装权限退出
						mInstaller.setPermissionsResult(mSessionId, true);
						clearCachedApkIfNeededAndFinish();
					} 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);
				}
				clearCachedApkIfNeededAndFinish();
			}
		}

1.9 进入安装

private void startInstall() {
			// Start subactivity to actually install the application
			Intent newIntent = new Intent();
			//带走安装包的applicationInfo
			newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO, mPkgInfo.applicationInfo);
			newIntent.setData(mPackageURI); //带走安装包的applicationInfo
			newIntent.setClass(this, InstallAppProgress.class);
			String installerPackageName = getIntent().getStringExtra( Intent.EXTRA_INSTALLER_PACKAGE_NAME);
			if (mOriginatingURI != null) { //带走安装包的mOriginatingURI
				newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
			}
			if (mReferrerURI != null) {//带走安装包的mReferrerURI
				newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
			}
			if (mOriginatingUid != VerificationParams.NO_UID) {//带走安装包的mOriginatingUid,这个uid如果不是拉安装的应用的uid
				newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
			}
			if (installerPackageName != null) {//带走安装包的installerPackageName
				newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, installerPackageName);
			}
			if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
				newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
				newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
			}
			if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
			startActivity(newIntent);
			finish();
		}

2  安装过程InstallAppProgress

2.1 注册安装监听

IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(BROADCAST_ACTION);
    registerReceiver(mBroadcastReceiver, intentFilter, BROADCAST_SENDER_PERMISSION, null /*scheduler*/);

2.2  正式安装

替换现存的包标示
    final int installFlags = getInstallFlags(mAppInfo.packageName);
if ("package".equals(mPackageURI.getScheme())) {
            try {
                //安装与该应用同名的应用,应该比较快,否则会抛出异常
                pm.installExistingPackage(mAppInfo.packageName);
                onPackageInstalled(PackageInstaller.STATUS_SUCCESS);
            } catch (PackageManager.NameNotFoundException e) {
                onPackageInstalled(PackageInstaller.STATUS_FAILURE_INVALID);
            }
        } else {
            final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                    PackageInstaller.SessionParams.MODE_FULL_INSTALL);
            params.referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
            params.originatingUri = getIntent().getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
            params.originatingUid = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID, UID_UNKNOWN);
            File file = new File(mPackageURI.getPath());
            try {
				//解析安装包,设置安装位置。这个安装位置是从AndroidManifest文件获取的,至于怎么获取,最后指向native 层。没有继续跟踪
                params.setInstallLocation(PackageParser.parsePackageLite(file, 0).installLocation);
            } catch (PackageParser.PackageParserException e) {
                Log.e(TAG, "Cannot parse package " + file + ". Assuming defaults.");
            }

            mInstallHandler.post(new Runnable() {
                @Override
                public void run() {
                    doPackageStage(pm, params);
                }
            });
        }

2.3 后台安装

private void doPackageStage(PackageManager pm, PackageInstaller.SessionParams params) {
        //初始化安装器
        final PackageInstaller packageInstaller = pm.getPackageInstaller();
        PackageInstaller.Session session = null;
        try {
            final String packageLocation = mPackageURI.getPath();
            final File file = new File(packageLocation);
            //获取sessionId
            final int sessionId = packageInstaller.createSession(params);
            final byte[] buffer = new byte[65536];
            //获取session
            session = packageInstaller.openSession(sessionId);
            final InputStream in = new FileInputStream(file);
            final long sizeBytes = file.length();
            final OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes);
            try {
                int c;
                //安装中..............
                while ((c = in.read(buffer)) != -1) {
                    out.write(buffer, 0, c);
                    if (sizeBytes > 0) {
                        final float fraction = ((float) c / (float) sizeBytes);
                        session.addProgress(fraction);
                    }
                }
                session.fsync(out);
            } finally {
                IoUtils.closeQuietly(in);
                IoUtils.closeQuietly(out);
            }
            // Create a PendingIntent and use it to generate the IntentSender
            //发起安装完成提交通知
            Intent broadcastIntent = new Intent(BROADCAST_ACTION);
            PendingIntent pendingIntent = PendingIntent.getBroadcast(
                    InstallAppProgress.this /*context*/,
                    sessionId,
                    broadcastIntent,
                    PendingIntent.FLAG_UPDATE_CURRENT);
            session.commit(pendingIntent.getIntentSender());
        } catch (IOException e) {
            onPackageInstalled(PackageInstaller.STATUS_FAILURE);
        } finally {
            IoUtils.closeQuietly(session);
        }
    }

2.4  接受安装结果

  private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final int statusCode = intent.getIntExtra(
                    PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
            //等待安装
            if (statusCode == PackageInstaller.STATUS_PENDING_USER_ACTION) {
                context.startActivity((Intent)intent.getParcelableExtra(Intent.EXTRA_INTENT));
            } else {
				//返回安装结果
                onPackageInstalled(statusCode);
            }
        }
    };

Android 7.0 安装器安装过程分析 (com.android.packageinstaller)的更多相关文章

  1. .Net 转战 Android 4.4 日常笔记(5)--新软件Android Studio 0.5.8安装与配置及问题解决

    说真心话,Eclipse跟我们.net的VS比起来就是屌丝比高富帅,一切都是那么的难用,速度慢得我无法忍受 于是想试试Google钦点的Android Studio IDE工具,这跟ADT一样也是一套 ...

  2. 在Android Studio 0.5.2中使用ArcGIS Android SDK

    环境 操作系统:Mac OSX 10.8.5Android Studio: 0.5.2ArcGIS Android SDK: 10.2.3 操作步骤 在Android Studio中新建一个Modul ...

  3. Android 7.0 调取系统相机崩溃解决android.os.FileUriExposedException

    一.写在前面 最近由于廖子尧忙于自己公司的事情和OkGo(一款专注于让网络请求更简单的网络框架) ,故让LZ 接替维护ImagePicker(一款支持单.多选.旋转和裁剪的图片选择器),也是处理了诸多 ...

  4. 【适配整理】Android 7.0 调取系统相机崩溃解决android.os.FileUriExposedException

    一.写在前面 最近由于廖子尧忙于自己公司的事情和 OkGo (一款专注于让网络请求更简单的网络框架) ,故让LZ 接替维护 ImagePicker(一款支持单.多选.旋转和裁剪的图片选择器),也是处理 ...

  5. 【Android Studio安装部署系列】三十五、从Android studio3.0.1升级到Android studio3.1.4【以及创建android p模拟器的尝试(未成功)】

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 概述 因为想要使用Android P模拟器,所以需要将Android Studio升级到3.1版本以上. Android P模拟器的最低版 ...

  6. [php-pear]如何使用 PHP-PEAR安装器,以及使用 PEAR 安装扩展库

    我们都知道 PHP PEAR,就是 PHP Extension and Application Respository,也就是 PHP 扩展和应用代码库. PHP 也可以通过 PEAR 安装器来进行 ...

  7. 谷歌安装器扫描时提示“需要root权限”,不用root也可以的!

    能FQ的用户会用谷歌服务,一般的新手机没有安装谷歌框架,但是在用谷歌安装器安装谷歌市场时会提示"需要root权限",我用的是360手机,按照下面的教程搞好了: 安装完GSM包就可以 ...

  8. android 5.0新特性

    Android Lollipop 面向开发人员的主要功能 Material Design 设计 注重性能 通知 以大屏幕呈现 以文档为中心 连接性能再上一级 高性能图形 音频处理功能更强 摄像头和视频 ...

  9. Android 8.0 功能和 API

    Android 8.0 为用户和开发者引入多种新功能.本文重点介绍面向开发者的新功能. 用户体验 通知 在 Android 8.0 中,我们已重新设计通知,以便为管理通知行为和设置提供更轻松和更统一的 ...

随机推荐

  1. realmock 前后端分离方案

    realmock 前后端分离方案 express + randomjson 模拟后端服务,前端服务器(比如webpack, nigix等)将请求代理到该服务器地址即可 github地址:https:/ ...

  2. Android自定义评分控件:RatingStarView

    RatingStarView Android自定义的评分控件,类似ProgressBar那样的,使用星星图标(full.half.empty)作为progress标识的评分/打分控件. 效果图 图1: ...

  3. JS中作用域

    var scope = 'global'; var f = function() { console.log(scope); // 输出 undefined var scope = 'f'; } f( ...

  4. 算法问题:最长滑道问题(非递归,C++)

    题目描述请参考博客http://blog.csdn.net/sinat_30186009/article/details/52356053,在此表示感谢. 基本思路参考了以上文章,但是上面文章中的算法 ...

  5. LISTCTRL控件方法

    以下未经说明,listctrl默认view风格为report --------------------------------------------------------------------- ...

  6. 通过winform+模拟登录实现快速一键登录到人才招聘网站

    之前为了便于人事部门招聘登录网站更简洁高效,免去每天频繁输网址.用户名.密码等相关登录信息,特基于winform+HttpWebRequest实现模拟请求登录,最终达到一键登录到招聘网站后台的效果. ...

  7. springcloud(四):熔断器Hystrix

    说起springcloud熔断让我想起了去年股市中的熔断,多次痛的领悟,随意实施的熔断对整个系统的影响是灾难性的,好了接下来我们还是说正事. 熔断器 雪崩效应 在微服务架构中通常会有多个服务层调用,基 ...

  8. CentOS 7.2下安装Mono 5.0

    微软Build2017大会期间.NET领域的.NET core之外,就是Visual Studio For Mac,大家都知道Visual Studio For Mac 是基于Mono运行的,Mono ...

  9. 数据结构与算法系列研究五——树、二叉树、三叉树、平衡排序二叉树AVL

    树.二叉树.三叉树.平衡排序二叉树AVL 一.树的定义 树是计算机算法最重要的非线性结构.树中每个数据元素至多有一个直接前驱,但可以有多个直接后继.树是一种以分支关系定义的层次结构.    a.树是n ...

  10. Basic Sort Algorithms

    1. Bubble Sort public void bubbleSort(int[] arr) { boolean swapped = true; int j = 0; int tmp; while ...