您现在的位置是:首页 > 文章详情

安卓应用安全指南 5.5.1 处理隐私数据 示例代码

日期:2018-04-04点击:344

5.5.1 处理隐私数据 示例代码

原书:Android Application Secure Design/Secure Coding Guidebook

译者:飞龙

协议:CC BY-NC-SA 4.0

在准备应用的隐私政策时,你可以使用“协助创建应用隐私政策的工具” [29]。 这些工具以 HTML 格式和 XML 格式输出两个文件 - 应用隐私策略的摘要版本和详细版本。 这些文件的 HTML 和 XML 内容符合 MIC SPI 的建议,包括搜索标签等特性。 在下面的示例代码中,我们将演示此工具的用法,并使用由这个工具产生的 HTML 文件来展示程序隐私策略。

[29] http://www.kddilabs.jp/tech/public-tech/appgen.html

更具体地说,你可以使用以下流程图来确定使用哪个示例代码。

这里,“广泛同意”一词,指代广泛许可,由用户在应用的首次加载时,通过展示和查看程序隐私策略授予应用,用于应用将用户数据传输到服务器。 相反,短语“特定同意”指代在传输特定用户数据之前,立即获得的预先同意。

5.5.1.1 授予广泛同意和特定同意:包含应用隐私政策的应用

要点:

  1. 首次加载(或应用更新)时,获得广泛同意,来传输将由应用处理的用户数据。
  2. 如果用户未授予广泛同意,请勿传输用户数据。
  3. 在传输需要特别细致的处理的用户数据之前获得特定同意。
  4. 如果用户未授予特定同意,请勿传输相应的数据。
  5. 向用户提供可以查看应用隐私策略的方法。
  6. 提供通过用户操作删除传输的数据的方法。
  7. 提供通过用户操作停止数据传输的方法。
  8. 使用 UUID 或 cookie 来跟踪用户数据。
  9. 将应用隐私策略的摘要版本放置在素材文件夹中。

MainActivity.java

package org.jssec.android.privacypolicy; import java.io.IOException; import org.json.JSONException; import org.json.JSONObject; import org.jssec.android.privacypolicy.ConfirmFragment.DialogListener; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesClient; import com.google.android.gms.common.GooglePlayServicesUtil; import com.google.android.gms.location.LocationClient; import android.location.Location; import android.os.AsyncTask; import android.os.Bundle; import android.content.Intent; import android.content.IntentSender; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.text.Editable; import android.text.TextWatcher; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends FragmentActivity implements GooglePlayServicesClient.ConnectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener, DialogListener { private static final String BASE_URL = "https://www.example.com/pp"; private static final String GET_ID_URI = BASE_URL + "/get_id.php"; private static final String SEND_DATA_URI = BASE_URL + "/send_data.php"; private static final String DEL_ID_URI = BASE_URL + "/del_id.php"; private static final String ID_KEY = "id"; private static final String LOCATION_KEY = "location"; private static final String NICK_NAME_KEY = "nickname"; private static final String PRIVACY_POLICY_COMPREHENSIVE_AGREED_KEY = "privacyPolicyComprehensiveAgreed"; private static final String PRIVACY_POLICY_DISCRETE_TYPE1_AGREED_KEY = "privacyPolicyDiscreteType1Agreed"; private static final String PRIVACY_POLICY_PREF_NAME = "privacypolicy_preference"; private static final int CONNECTION_FAILURE_RESOLUTION_REQUEST = 257; private String UserId = ""; private LocationClient mLocationClient = null; private final int DIALOG_TYPE_COMPREHENSIVE_AGREEMENT = 1; private final int DIALOG_TYPE_PRE_CONFIRMATION = 2; private static final int VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW = 1; private TextWatcher watchHandler = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { boolean buttonEnable = (s.length() > 0); MainActivity.this.findViewById(R.id.buttonStart).setEnabled(buttonEnable); } @Override public void afterTextChanged(Editable s) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Fetch user ID from serverFetch user ID from server new GetDataAsyncTask().execute(); findViewById(R.id.buttonStart).setEnabled(false); ((TextView) findViewById(R.id.editTextNickname)).addTextChangedListener(watchHandler); int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this); if (resultCode == ConnectionResult.SUCCESS) { mLocationClient = new LocationClient(this, this, this); } } @Override protected void onStart() { super.onStart(); SharedPreferences pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE); int privacyPolicyAgreed = pref.getInt(PRIVACY_POLICY_COMPREHENSIVE_AGREED_KEY, -1); if (privacyPolicyAgreed <= VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW) { // *** POINT 1 *** On first launch (or application update), obtain broad consent to transmit user data that will be handled by the application. // When the application is updated, it is only necessary to renew the user's grant of broad c onsent if the updated application will handle new types of user data. ConfirmFragment dialog = ConfirmFragment.newInstance(R.string.privacyPolicy, R.string.agreeP rivacyPolicy, DIALOG_TYPE_COMPREHENSIVE_AGREEMENT); dialog.setDialogListener(this); FragmentManager fragmentManager = getSupportFragmentManager(); dialog.show(fragmentManager, "dialog"); } // Used to obtain location data if (mLocationClient != null) { mLocationClient.connect(); } } @Override protected void onStop() { if (mLocationClient != null) { mLocationClient.disconnect(); } super.onStop(); } public void onSendToServer(View view) { // Check the status of user consent. // Actually, it is necessary to obtain consent for each user data type. SharedPreferences pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE); int privacyPolicyAgreed = pref.getInt(PRIVACY_POLICY_DISCRETE_TYPE1_AGREED_KEY, -1); if (privacyPolicyAgreed <= VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW) { // *** POINT 3 *** Obtain specific consent before transmitting user data that requires particularly delicate handling. ConfirmFragment dialog = ConfirmFragment.newInstance(R.string.sendLocation, R.string.cofirmS endLocation, DIALOG_TYPE_PRE_CONFIRMATION); dialog.setDialogListener(this); FragmentManager fragmentManager = getSupportFragmentManager(); dialog.show(fragmentManager, "dialog"); } else { // Start transmission, since it has the user consent. onPositiveButtonClick(DIALOG_TYPE_PRE_CONFIRMATION); } } public void onPositiveButtonClick(int type) { if (type == DIALOG_TYPE_COMPREHENSIVE_AGREEMENT) { // *** POINT 1 *** On first launch (or application update), obtain broad consent to transmit user data that will be handled by the application. SharedPreferences.Editor pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE).edit(); pref.putInt(PRIVACY_POLICY_COMPREHENSIVE_AGREED_KEY, getVersionCode()); pref.apply(); } else if (type == DIALOG_TYPE_PRE_CONFIRMATION) { // *** POINT 3 *** Obtain specific consent before transmitting user data that requires particularly delicate handling. if (mLocationClient != null && mLocationClient.isConnected()) { Location currentLocation = mLocationClient.getLastLocation(); if (currentLocation != null) { String locationData = "Latitude:" + currentLocation.getLatitude() + ", Longitude:" + currentLocation.getLongitude(); String nickname = ((TextView) findViewById(R.id.editTextNickname)).getText().toString(); Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + "¥n - nickname : " + nickname + "¥n - location : " + locationData, Toast.LENGTH_SHORT).show(); new SendDataAsyncTack().execute(SEND_DATA_URI, UserId, locationData, nickname); } } // Store the status of user consent. // Actually, it is necessary to obtain consent for each user data type. SharedPreferences.Editor pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE).edit(); pref.putInt(PRIVACY_POLICY_DISCRETE_TYPE1_AGREED_KEY, getVersionCode()); pref.apply(); } } public void onNegativeButtonClick(int type) { if (type == DIALOG_TYPE_COMPREHENSIVE_AGREEMENT) { // *** POINT 2 *** If the user does not grant general consent, do not transmit user data. // In this sample application we terminate the application in this case. finish(); } else if (type == DIALOG_TYPE_PRE_CONFIRMATION) { // *** POINT 4 *** If the user does not grant specific consent, do not transmit the correspon ding data. // The user did not grant consent, so we do nothing. } } private int getVersionCode() { int versionCode = -1; PackageManager packageManager = this.getPackageManager(); try { PackageInfo packageInfo = packageManager.getPackageInfo(this.getPackageName(), PackageManager.GET_ACTIVITIES); versionCode = packageInfo.versionCode; } catch (NameNotFoundException e) { // This is sample, so omit the exception process } return versionCode; } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_show_pp: // *** POINT 5 *** Provide methods by which the user can review the application privacy policy. Intent intent = new Intent(); intent.setClass(this, WebViewAssetsActivity.class); startActivity(intent); return true; case R.id.action_del_id: // *** POINT 6 *** Provide methods by which transmitted data can be deleted by user operations. new SendDataAsyncTack().execute(DEL_ID_URI, UserId); return true; case R.id.action_donot_send_id: // *** POINT 7 *** Provide methods by which transmitting data can be stopped by user operations. // If the user stop sending data, user consent is deemed to have been revoked. SharedPreferences.Editor pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE).edit(); pref.putInt(PRIVACY_POLICY_COMPREHENSIVE_AGREED_KEY, 0); pref.apply(); // In this sample application if the user data cannot be sent by user operations, // finish the application because we do nothing. String message = getString(R.string.stopSendUserData); Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + message, Toast.LENGTH_SHORT).show(); finish(); return true; } return false; } @Override public void onConnected(Bundle connectionHint) { if (mLocationClient != null && mLocationClient.isConnected()) { Location currentLocation = mLocationClient.getLastLocation(); if (currentLocation != null) { String locationData = "Latitude ¥t: " + currentLocation.getLatitude() + "¥n¥tLongitude ¥t: " + currentLocation.getLongitude(); String text = "¥n" + getString(R.string.your_location_title) + "¥n¥t" + locationData; TextView appText = (TextView) findViewById(R.id.appText); appText.setText(text); } } } @Override public void onConnectionFailed(ConnectionResult result) { if (result.hasResolution()) { try { result.startResolutionForResult(this, CONNECTION_FAILURE_RESOLUTION_REQUEST); } catch (IntentSender.SendIntentException e) { e.printStackTrace(); } } } @Override public void onDisconnected() { mLocationClient = null; } private class GetDataAsyncTask extends AsyncTask<String, Void, String> { private String extMessage = ""; @Override protected String doInBackground(String... params) { // *** POINT 8 *** Use UUIDs or cookies to keep track of user data // In this sample we use an ID generated on the server side SharedPreferences sp = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE); UserId = sp.getString(ID_KEY, null); if (UserId == null) { // No token in SharedPreferences; fetch ID from server try { UserId = NetworkUtil.getCookie(GET_ID_URI, "", "id"); } catch (IOException e) { // Catch exceptions such as certification errors extMessage = e.toString(); } // Store the fetched ID in SharedPreferences sp.edit().putString(ID_KEY, UserId).commit(); } return UserId; } @Override protected void onPostExecute(final String data) { String status = (data != null) ? "success" : "error"; Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + status + " : " + extMessage, Toast.LENGTH_SHORT).show(); } } private class SendDataAsyncTack extends AsyncTask<String, Void, Boolean> { private String extMessage = ""; @Override protected Boolean doInBackground(String... params) { String url = params[0]; String id = params[1]; String location = params.length > 2 ? params[2] : null; String nickname = params.length > 3 ? params[3] : null; Boolean result = false; try { JSONObject jsonData = new JSONObject(); jsonData.put(ID_KEY, id); if (location != null) jsonData.put(LOCATION_KEY, location); if (nickname != null) jsonData.put(NICK_NAME_KEY, nickname); NetworkUtil.sendJSON(url, "", jsonData.toString()); result = true; } catch (IOException e) { // Catch exceptions such as certification errors extMessage = e.toString(); } catch (JSONException e) { extMessage = e.toString(); } return result; } @Override protected void onPostExecute(Boolean result) { String status = result ? "Success" : "Error"; Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + status + " : " + extMessage, Toast.LENGTH_SHORT).show(); } } }

ConfirmFragment.java

package org.jssec.android.privacypolicy; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.widget.TextView; public class ConfirmFragment extends DialogFragment { private DialogListener mListener = null; public static interface DialogListener { public void onPositiveButtonClick(int type); public void onNegativeButtonClick(int type); } public static ConfirmFragment newInstance(int title, int sentence, int type) { ConfirmFragment fragment = new ConfirmFragment(); Bundle args = new Bundle(); args.putInt("title", title); args.putInt("sentence", sentence); args.putInt("type", type); fragment.setArguments(args); return fragment; } @Override public Dialog onCreateDialog(Bundle args) { // *** POINT 1 *** On first launch (or application update), obtain broad consent to transmit user data that will be handled by the application. // *** POINT 3 *** Obtain specific consent before transmitting user data that requires particularly delicate handling. final int title = getArguments().getInt("title"); final int sentence = getArguments().getInt("sentence"); final int type = getArguments().getInt("type"); LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); View content = inflater.inflate(R.layout.fragment_comfirm, null); TextView linkPP = (TextView) content.findViewById(R.id.tx_link_pp); linkPP.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // *** POINT 5 *** Provide methods by which the user can review the application privacy policy. Intent intent = new Intent(); intent.setClass(getActivity(), WebViewAssetsActivity.class); startActivity(intent); } }); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setIcon(R.drawable.ic_launcher); builder.setTitle(title); builder.setMessage(sentence); builder.setView(content); builder.setPositiveButton(R.string.buttonConsent, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { if (mListener != null) { mListener.onPositiveButtonClick(type); } } }); builder.setNegativeButton(R.string.buttonDonotConsent, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { if (mListener != null) { mListener.onNegativeButtonClick(type); } } }); Dialog dialog = builder.create(); dialog.setCanceledOnTouchOutside(false); return dialog; } @Override public void onAttach(Activity activity) { super.onAttach(activity); if (!(activity instanceof DialogListener)) { throw new ClassCastException(activity.toString() + " must implement DialogListener."); } mListener = (DialogListener) activity; } public void setDialogListener(DialogListener listener) { mListener = listener; } }

WebViewAssetsActivity.java

package org.jssec.android.privacypolicy; import android.app.Activity; import android.os.Bundle; import android.webkit.WebSettings; import android.webkit.WebView; public class WebViewAssetsActivity extends Activity { // *** POINT 9 *** Place a summary version of the application privacy policy in the assets folder private static final String ABST_PP_URL = "file:///android_asset/PrivacyPolicy/app-policy-abst-privacypolicy-1.0.html"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_webview); WebView webView = (WebView) findViewById(R.id.webView); WebSettings webSettings = webView.getSettings(); webSettings.setAllowFileAccess(false); webView.loadUrl(ABST_PP_URL); } }

5.5.1.2 授予广泛同意:包含应用隐私政策的应用

要点:

  1. 首次加载(或应用更新)时,获得广泛同意,来传输将由应用处理的用户数据。
  2. 如果用户未授予广泛同意,请勿传输用户数据。
  3. 向用户提供可以查看应用隐私策略的方法。
  4. 提供通过用户操作删除传输的数据的方法。
  5. 提供通过用户操作停止数据传输的方法。
  6. 使用 UUID 或 cookie 来跟踪用户数据。
  7. 将应用隐私策略的摘要版本放置在素材文件夹中。

MainActivity.java

package org.jssec.android.privacypolicynopreconfirm; import java.io.IOException; import org.json.JSONException; import org.json.JSONObject; import org.jssec.android.privacypolicynopreconfirm.MainActivity; import org.jssec.android.privacypolicynopreconfirm.R; import org.jssec.android.privacypolicynopreconfirm.ConfirmFragment.DialogListener; import android.os.AsyncTask; import android.os.Bundle; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.telephony.TelephonyManager; import android.text.Editable; import android.text.TextWatcher; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends FragmentActivity implements DialogListener { private final String BASE_URL = "https://www.example.com/pp"; private final String GET_ID_URI = BASE_URL + "/get_id.php"; private final String SEND_DATA_URI = BASE_URL + "/send_data.php"; private final String DEL_ID_URI = BASE_URL + "/del_id.php"; private final String ID_KEY = "id"; private final String NICK_NAME_KEY = "nickname"; private final String IMEI_KEY = "imei"; private final String PRIVACY_POLICY_AGREED_KEY = "privacyPolicyAgreed"; private final String PRIVACY_POLICY_PREF_NAME = "privacypolicy_preference"; private String UserId = ""; private final int DIALOG_TYPE_COMPREHENSIVE_AGREEMENT = 1; private final int VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW = 1; private TextWatcher watchHandler = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { boolean buttonEnable = (s.length() > 0); MainActivity.this.findViewById(R.id.buttonStart).setEnabled(buttonEnable); } @Override public void afterTextChanged(Editable s) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Fetch user ID from serverFetch user ID from server new GetDataAsyncTask().execute(); findViewById(R.id.buttonStart).setEnabled(false); ((TextView) findViewById(R.id.editTextNickname)).addTextChangedListener(watchHandler); } @Override protected void onStart() { super.onStart(); SharedPreferences pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE); int privacyPolicyAgreed = pref.getInt(PRIVACY_POLICY_AGREED_KEY, -1); if (privacyPolicyAgreed <= VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW) { // *** POINT 1 *** On first launch (or application update), obtain broad consent to transmit user data that will be handled by the application. // When the application is updated, it is only necessary to renew the user's grant of broad consent if the updated application will handle new types of user data. ConfirmFragment dialog = ConfirmFragment.newInstance(R.string.privacyPolicy, R.string.agreePr ivacyPolicy, DIALOG_TYPE_COMPREHENSIVE_AGREEMENT); dialog.setDialogListener(this); FragmentManager fragmentManager = getSupportFragmentManager(); dialog.show(fragmentManager, "dialog"); } } public void onSendToServer(View view) { String nickname = ((TextView) findViewById(R.id.editTextNickname)).getText().toString(); TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); String imei = tm.getDeviceId(); Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + "¥n - nickname : " + nickname + ", imei = " + imei, Toast.LENGTH_SHORT).show(); new SendDataAsyncTack().execute(SEND_DATA_URI, UserId, nickname, imei); } public void onPositiveButtonClick(int type) { if (type == DIALOG_TYPE_COMPREHENSIVE_AGREEMENT) { // *** POINT 1 *** On first launch (or application update), obtain broad consent to transmit user data that will be handled by the application. SharedPreferences.Editor pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE).edit(); pref.putInt(PRIVACY_POLICY_AGREED_KEY, getVersionCode()); pref.apply(); } } public void onNegativeButtonClick(int type) { if (type == DIALOG_TYPE_COMPREHENSIVE_AGREEMENT) { // *** POINT 2 *** If the user does not grant general consent, do not transmit user data. // In this sample application we terminate the application in this case. finish(); } } private int getVersionCode() { int versionCode = -1; PackageManager packageManager = this.getPackageManager(); try { PackageInfo packageInfo = packageManager.getPackageInfo(this.getPackageName(), PackageManager.GET_ACTIVITIES); versionCode = packageInfo.versionCode; } catch (NameNotFoundException e) { // This is sample, so omit the exception process } return versionCode; } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_show_pp: // *** POINT 3 *** Provide methods by which the user can review the application privacy policy. Intent intent = new Intent(); intent.setClass(this, WebViewAssetsActivity.class); startActivity(intent); return true; case R.id.action_del_id: // *** POINT 4 *** Provide methods by which transmitted data can be deleted by user operation s. new SendDataAsyncTack().execute(DEL_ID_URI, UserId); return true; case R.id.action_donot_send_id: // *** POINT 5 *** Provide methods by which transmitting data can be stopped by user operations. // If the user stop sending data, user consent is deemed to have been revoked. SharedPreferences.Editor pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE).edit(); pref.putInt(PRIVACY_POLICY_AGREED_KEY, 0); pref.apply(); // In this sample application if the user data cannot be sent by user operations, // finish the application because we do nothing. String message = getString(R.string.stopSendUserData); Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + message, Toast.L ENGTH_SHORT).show(); finish(); return true; } return false; } private class GetDataAsyncTask extends AsyncTask<String, Void, String> { private String extMessage = ""; @Override protected String doInBackground(String... params) { // *** POINT 6 *** Use UUIDs or cookies to keep track of user data // In this sample we use an ID generated on the server side SharedPreferences sp = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE); UserId = sp.getString(ID_KEY, null); if (UserId == null) { // No token in SharedPreferences; fetch ID from server try { UserId = NetworkUtil.getCookie(GET_ID_URI, "", "id"); } catch (IOException e) { // Catch exceptions such as certification errors extMessage = e.toString(); } // Store the fetched ID in SharedPreferences sp.edit().putString(ID_KEY, UserId).commit(); } return UserId; } @Override protected void onPostExecute(final String data) { String status = (data != null) ? "success" : "error"; Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + status + " : " + extMessage, Toast.LENGTH_SHORT).show(); } } private class SendDataAsyncTack extends AsyncTask<String, Void, Boolean> { private String extMessage = ""; @Override protected Boolean doInBackground(String... params) { String url = params[0]; String id = params[1]; String nickname = params.length > 2 ? params[2] : null; String imei = params.length > 3 ? params[3] : null; Boolean result = false; try { JSONObject jsonData = new JSONObject(); jsonData.put(ID_KEY, id); if (nickname != null) jsonData.put(NICK_NAME_KEY, nickname); if (imei != null) jsonData.put(IMEI_KEY, imei); NetworkUtil.sendJSON(url, "", jsonData.toString()); result = true; } catch (IOException e) { // Catch exceptions such as certification errors extMessage = e.toString(); } catch (JSONException e) { extMessage = e.toString(); } return result; } @Override protected void onPostExecute(Boolean result) { String status = result ? "Success" : "Error"; Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + status + " : " + extMessage, Toast.LENGTH_SHORT).show(); } } }

ConfirmFragment.java

package org.jssec.android.privacypolicynopreconfirm; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.widget.TextView; public class ConfirmFragment extends DialogFragment { private DialogListener mListener = null; public static interface DialogListener { public void onPositiveButtonClick(int type); public void onNegativeButtonClick(int type); } public static ConfirmFragment newInstance(int title, int sentence, int type) { ConfirmFragment fragment = new ConfirmFragment(); Bundle args = new Bundle(); args.putInt("title", title); args.putInt("sentence", sentence); args.putInt("type", type); fragment.setArguments(args); return fragment; } @Override public Dialog onCreateDialog(Bundle args) { // *** POINT 1 *** On first launch (or application update), obtain broad consent to transmit user data that will be handled by the application. final int title = getArguments().getInt("title"); final int sentence = getArguments().getInt("sentence"); final int type = getArguments().getInt("type"); LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); View content = inflater.inflate(R.layout.fragment_comfirm, null); TextView linkPP = (TextView) content.findViewById(R.id.tx_link_pp); linkPP.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // *** POINT 3 *** Provide methods by which the user can review the application privacy policy. Intent intent = new Intent(); intent.setClass(getActivity(), WebViewAssetsActivity.class); startActivity(intent); } }); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setIcon(R.drawable.ic_launcher); builder.setTitle(title); builder.setMessage(sentence); builder.setView(content); builder.setPositiveButton(R.string.buttonConsent, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { if (mListener != null) { mListener.onPositiveButtonClick(type); } } }); builder.setNegativeButton(R.string.buttonDonotConsent, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { if (mListener != null) { mListener.onNegativeButtonClick(type); } } }); Dialog dialog = builder.create(); dialog.setCanceledOnTouchOutside(false); return dialog; } @Override public void onAttach(Activity activity) { super.onAttach(activity); if (!(activity instanceof DialogListener)) { throw new ClassCastException(activity.toString() + " must implement DialogListener."); } mListener = (DialogListener) activity; } public void setDialogListener(DialogListener listener) { mListener = listener; } }

WebViewAssetsActivity.java

package org.jssec.android.privacypolicynopreconfirm; import org.jssec.android.privacypolicynopreconfirm.R; import android.app.Activity; import android.os.Bundle; import android.webkit.WebSettings; import android.webkit.WebView; public class WebViewAssetsActivity extends Activity { // *** POINT 7 *** Place a summary version of the application privacy policy in the assets folder private final String ABST_PP_URL = "file:///android_asset/PrivacyPolicy/app-policy-abst-privacypolicy-1.0.html"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_webview); WebView webView = (WebView) findViewById(R.id.webView); WebSettings webSettings = webView.getSettings(); webSettings.setAllowFileAccess(false); webView.loadUrl(ABST_PP_URL); } }

5.5.1.3 不需要广泛同意:包含应用隐私策略的应用

要点:

  1. 向用户提供查看应用隐私策略的方法。
  2. 提供通过用户操作删除传输的数据的方法。
  3. 提供通过用户操作停止数据传输的方法
  4. 使用 UUID 或 cookie 来跟踪用户数据。
  5. 将应用隐私策略的摘要版本放置在素材文件夹中。

MainActivity.java

package org.jssec.android.privacypolicynocomprehensive; import java.io.IOException; import org.json.JSONException; import org.json.JSONObject; import android.os.AsyncTask; import android.os.Bundle; import android.content.Intent; import android.content.SharedPreferences; import android.support.v4.app.FragmentActivity; import android.text.Editable; import android.text.TextWatcher; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends FragmentActivity { private static final String BASE_URL = "https://www.example.com/pp"; private static final String GET_ID_URI = BASE_URL + "/get_id.php"; private static final String SEND_DATA_URI = BASE_URL + "/send_data.php"; private static final String DEL_ID_URI = BASE_URL + "/del_id.php"; private static final String ID_KEY = "id"; private static final String NICK_NAME_KEY = "nickname"; private static final String PRIVACY_POLICY_PREF_NAME = "privacypolicy_preference"; private String UserId = ""; private TextWatcher watchHandler = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { boolean buttonEnable = (s.length() > 0); MainActivity.this.findViewById(R.id.buttonStart).setEnabled(buttonEnable); } @Override public void afterTextChanged(Editable s) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Fetch user ID from serverFetch user ID from server new GetDataAsyncTask().execute(); findViewById(R.id.buttonStart).setEnabled(false); ((TextView) findViewById(R.id.editTextNickname)).addTextChangedListener(watchHandler); } public void onSendToServer(View view) { String nickname = ((TextView) findViewById(R.id.editTextNickname)).getText().toString(); Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + "¥n - nickname : " + nickname, Toast.LENGTH_SHORT).show(); new sendDataAsyncTack().execute(SEND_DATA_URI, UserId, nickname); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_show_pp: // *** POINT 1 *** Provide methods by which the user can review the application privacy policy. Intent intent = new Intent(); intent.setClass(this, WebViewAssetsActivity.class); startActivity(intent); return true; case R.id.action_del_id: // *** POINT 2 *** Provide methods by which transmitted data can be deleted by user operations. new sendDataAsyncTack().execute(DEL_ID_URI, UserId); return true; case R.id.action_donot_send_id: // *** POINT 3 *** Provide methods by which transmitting data can be stopped by user operations. // In this sample application if the user data cannot be sent by user operations, // finish the application because we do nothing. String message = getString(R.string.stopSendUserData); Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + message, Toast.LENGTH_SHORT).show(); finish(); return true; } return false; } private class GetDataAsyncTask extends AsyncTask<String, Void, String> { private String extMessage = ""; @Override protected String doInBackground(String... params) { // *** POINT 4 *** Use UUIDs or cookies to keep track of user data // In this sample we use an ID generated on the server side SharedPreferences sp = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE); UserId = sp.getString(ID_KEY, null); if (UserId == null) { // No token in SharedPreferences; fetch ID from server try { UserId = NetworkUtil.getCookie(GET_ID_URI, "", "id"); } catch (IOException e) { // Catch exceptions such as certification errors extMessage = e.toString(); } // Store the fetched ID in SharedPreferences sp.edit().putString(ID_KEY, UserId).commit(); } return UserId; } @Override protected void onPostExecute(final String data) { String status = (data != null) ? "success" : "error"; Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + status + " : " + extMessage, Toast.LENGTH_SHORT).show(); } } private class sendDataAsyncTack extends AsyncTask<String, Void, Boolean> { private String extMessage = ""; @Override protected Boolean doInBackground(String... params) { String url = params[0]; String id = params[1]; String nickname = params.length > 2 ? params[2] : null; Boolean result = false; try { JSONObject jsonData = new JSONObject(); jsonData.put(ID_KEY, id); if (nickname != null) jsonData.put(NICK_NAME_KEY, nickname); NetworkUtil.sendJSON(url, "", jsonData.toString()); result = true; } catch (IOException e) { // Catch exceptions such as certification errors extMessage = e.toString(); } catch (JSONException e) { extMessage = e.toString(); } return result; } @Override protected void onPostExecute(Boolean result) { String status = result ? "Success" : "Error"; Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + status + " : " + extMessage, Toast.LENGTH_SHORT).show(); } } }

WebViewAssetsActivity.java

package org.jssec.android.privacypolicynocomprehensive; import org.jssec.android.privacypolicynocomprehensive.R; import android.app.Activity; import android.os.Bundle; import android.webkit.WebSettings; import android.webkit.WebView; public class WebViewAssetsActivity extends Activity { // *** POINT 5 *** Place a summary version of the application privacy policy in the assets folder private static final String ABST_PP_URL = "file:///android_asset/PrivacyPolicy/app-policy-abst-privacypolicy-1.0.html"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_webview); WebView webView = (WebView) findViewById(R.id.webView); WebSettings webSettings = webView.getSettings(); webSettings.setAllowFileAccess(false); webView.loadUrl(ABST_PP_URL); } }

5.5.1.4 不包含应用隐私策略的应用

要点:

  1. 如果你的应用只使用它在设备中获取的信息,则不需要显示应用隐私策略。
  2. 在市场应用或类似应用的文档中,请注意应用不会将其获取的信息传输到外部。

MainActivity.java

package org.jssec.android.privacypolicynoinfosent; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesClient; import com.google.android.gms.location.LocationClient; import android.location.Location; import android.net.Uri; import android.os.Bundle; import android.content.Intent; import android.content.IntentSender; import android.support.v4.app.FragmentActivity; import android.view.Menu; import android.view.View; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends FragmentActivity implements GooglePlayServicesClient.ConnectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener { private LocationClient mLocationClient = null; private final int CONNECTION_FAILURE_RESOLUTION_REQUEST = 257; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mLocationClient = new LocationClient(this, this, this); } @Override protected void onStart() { super.onStart(); // Used to obtain location data if (mLocationClient != null) { mLocationClient.connect(); } } @Override protected void onStop() { if (mLocationClient != null) { mLocationClient.disconnect(); } super.onStop(); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } public void onStartMap(View view) { // *** POINT 1 *** You do not need to display an application privacy policy if your application w ill only use the information it obtains within the device. if (mLocationClient != null && mLocationClient.isConnected()) { Location currentLocation = mLocationClient.getLastLocation(); if (currentLocation != null) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("geo:" + currentLocation.getLatitude() + "," + currentLocation.getLongitude())); startActivity(intent); } } } @Override public void onConnected(Bundle connectionHint) { if (mLocationClient != null && mLocationClient.isConnected()) { Location currentLocation = mLocationClient.getLastLocation(); if (currentLocation != null) { String locationData = "Latitude ¥t: " + currentLocation.getLatitude() + "¥n¥tLongitude ¥t: " + currentLocation.getLongitude(); String text = "¥n" + getString(R.string.your_location_title) + "¥n¥t" + locationData; Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + text, Toast.LENGTH_SHORT).show(); TextView appText = (TextView) findViewById(R.id.appText); appText.setText(text); } } } @Override public void onConnectionFailed(ConnectionResult result) { if (result.hasResolution()) { try { result.startResolutionForResult(this, CONNECTION_FAILURE_RESOLUTION_REQUEST); } catch (IntentSender.SendIntentException e) { e.printStackTrace(); } } } @Override public void onDisconnected() { mLocationClient = null; Toast.makeText(this, "Disconnected. Please re-connect.", Toast.LENGTH_SHORT).show(); } }

市场上的示例如下。

原文链接:https://yq.aliyun.com/articles/595241
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章