5.5.1 示例代码

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

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

5.5.1.md - 图1

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

5.5.1.md - 图2

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

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

要点:

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

MainActivity.java

  1. package org.jssec.android.privacypolicy;
  2. import java.io.IOException;
  3. import org.json.JSONException;
  4. import org.json.JSONObject;
  5. import org.jssec.android.privacypolicy.ConfirmFragment.DialogListener;
  6. import com.google.android.gms.common.ConnectionResult;
  7. import com.google.android.gms.common.GooglePlayServicesClient;
  8. import com.google.android.gms.common.GooglePlayServicesUtil;
  9. import com.google.android.gms.location.LocationClient;
  10. import android.location.Location;
  11. import android.os.AsyncTask;
  12. import android.os.Bundle;
  13. import android.content.Intent;
  14. import android.content.IntentSender;
  15. import android.content.SharedPreferences;
  16. import android.content.pm.PackageInfo;
  17. import android.content.pm.PackageManager;
  18. import android.content.pm.PackageManager.NameNotFoundException;
  19. import android.support.v4.app.FragmentActivity;
  20. import android.support.v4.app.FragmentManager;
  21. import android.text.Editable;
  22. import android.text.TextWatcher;
  23. import android.view.Menu;
  24. import android.view.MenuItem;
  25. import android.view.View;
  26. import android.widget.TextView;
  27. import android.widget.Toast;
  28. public class MainActivity extends FragmentActivity
  29. implements GooglePlayServicesClient.ConnectionCallbacks,
  30. GooglePlayServicesClient.OnConnectionFailedListener, DialogListener {
  31. private static final String BASE_URL = "https://www.example.com/pp";
  32. private static final String GET_ID_URI = BASE_URL + "/get_id.php";
  33. private static final String SEND_DATA_URI = BASE_URL + "/send_data.php";
  34. private static final String DEL_ID_URI = BASE_URL + "/del_id.php";
  35. private static final String ID_KEY = "id";
  36. private static final String LOCATION_KEY = "location";
  37. private static final String NICK_NAME_KEY = "nickname";
  38. private static final String PRIVACY_POLICY_COMPREHENSIVE_AGREED_KEY = "privacyPolicyComprehensiveAgreed";
  39. private static final String PRIVACY_POLICY_DISCRETE_TYPE1_AGREED_KEY = "privacyPolicyDiscreteType1Agreed";
  40. private static final String PRIVACY_POLICY_PREF_NAME = "privacypolicy_preference";
  41. private static final int CONNECTION_FAILURE_RESOLUTION_REQUEST = 257;
  42. private String UserId = "";
  43. private LocationClient mLocationClient = null;
  44. private final int DIALOG_TYPE_COMPREHENSIVE_AGREEMENT = 1;
  45. private final int DIALOG_TYPE_PRE_CONFIRMATION = 2;
  46. private static final int VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW = 1;
  47. private TextWatcher watchHandler = new TextWatcher() {
  48. @Override
  49. public void beforeTextChanged(CharSequence s, int start, int count, int after) {
  50. }
  51. @Override
  52. public void onTextChanged(CharSequence s, int start, int before, int count) {
  53. boolean buttonEnable = (s.length() > 0);
  54. MainActivity.this.findViewById(R.id.buttonStart).setEnabled(buttonEnable);
  55. }
  56. @Override
  57. public void afterTextChanged(Editable s) {
  58. }
  59. };
  60. @Override
  61. protected void onCreate(Bundle savedInstanceState) {
  62. super.onCreate(savedInstanceState);
  63. setContentView(R.layout.activity_main);
  64. // Fetch user ID from serverFetch user ID from server
  65. new GetDataAsyncTask().execute();
  66. findViewById(R.id.buttonStart).setEnabled(false);
  67. ((TextView) findViewById(R.id.editTextNickname)).addTextChangedListener(watchHandler);
  68. int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
  69. if (resultCode == ConnectionResult.SUCCESS) {
  70. mLocationClient = new LocationClient(this, this, this);
  71. }
  72. }
  73. @Override
  74. protected void onStart() {
  75. super.onStart();
  76. SharedPreferences pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE);
  77. int privacyPolicyAgreed = pref.getInt(PRIVACY_POLICY_COMPREHENSIVE_AGREED_KEY, -1);
  78. if (privacyPolicyAgreed <= VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW) {
  79. // *** POINT 1 *** On first launch (or application update), obtain broad consent to transmit user data that will be handled by the application.
  80. // When the application is updated, it is only necessary to renew the user's grant of broad c
  81. onsent if the updated application will handle new types of user data.
  82. ConfirmFragment dialog = ConfirmFragment.newInstance(R.string.privacyPolicy, R.string.agreeP
  83. rivacyPolicy, DIALOG_TYPE_COMPREHENSIVE_AGREEMENT);
  84. dialog.setDialogListener(this);
  85. FragmentManager fragmentManager = getSupportFragmentManager();
  86. dialog.show(fragmentManager, "dialog");
  87. }
  88. // Used to obtain location data
  89. if (mLocationClient != null) {
  90. mLocationClient.connect();
  91. }
  92. }
  93. @Override
  94. protected void onStop() {
  95. if (mLocationClient != null) {
  96. mLocationClient.disconnect();
  97. }
  98. super.onStop();
  99. }
  100. public void onSendToServer(View view) {
  101. // Check the status of user consent.
  102. // Actually, it is necessary to obtain consent for each user data type.
  103. SharedPreferences pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE);
  104. int privacyPolicyAgreed = pref.getInt(PRIVACY_POLICY_DISCRETE_TYPE1_AGREED_KEY, -1);
  105. if (privacyPolicyAgreed <= VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW) {
  106. // *** POINT 3 *** Obtain specific consent before transmitting user data that requires particularly delicate handling.
  107. ConfirmFragment dialog = ConfirmFragment.newInstance(R.string.sendLocation, R.string.cofirmS
  108. endLocation, DIALOG_TYPE_PRE_CONFIRMATION);
  109. dialog.setDialogListener(this);
  110. FragmentManager fragmentManager = getSupportFragmentManager();
  111. dialog.show(fragmentManager, "dialog");
  112. } else {
  113. // Start transmission, since it has the user consent.
  114. onPositiveButtonClick(DIALOG_TYPE_PRE_CONFIRMATION);
  115. }
  116. }
  117. public void onPositiveButtonClick(int type) {
  118. if (type == DIALOG_TYPE_COMPREHENSIVE_AGREEMENT) {
  119. // *** POINT 1 *** On first launch (or application update), obtain broad consent to transmit user data that will be handled by the application.
  120. SharedPreferences.Editor pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE).edit();
  121. pref.putInt(PRIVACY_POLICY_COMPREHENSIVE_AGREED_KEY, getVersionCode());
  122. pref.apply();
  123. } else if (type == DIALOG_TYPE_PRE_CONFIRMATION) {
  124. // *** POINT 3 *** Obtain specific consent before transmitting user data that requires particularly delicate handling.
  125. if (mLocationClient != null && mLocationClient.isConnected()) {
  126. Location currentLocation = mLocationClient.getLastLocation();
  127. if (currentLocation != null) {
  128. String locationData = "Latitude:" + currentLocation.getLatitude() + ", Longitude:" +
  129. currentLocation.getLongitude();
  130. String nickname = ((TextView) findViewById(R.id.editTextNickname)).getText().toString();
  131. Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + n - nickname :
  132. " + nickname + "¥n - location : " + locationData, Toast.LENGTH_SHORT).show();
  133. new SendDataAsyncTack().execute(SEND_DATA_URI, UserId, locationData, nickname);
  134. }
  135. }
  136. // Store the status of user consent.
  137. // Actually, it is necessary to obtain consent for each user data type.
  138. SharedPreferences.Editor pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE).edit();
  139. pref.putInt(PRIVACY_POLICY_DISCRETE_TYPE1_AGREED_KEY, getVersionCode());
  140. pref.apply();
  141. }
  142. }
  143. public void onNegativeButtonClick(int type) {
  144. if (type == DIALOG_TYPE_COMPREHENSIVE_AGREEMENT) {
  145. // *** POINT 2 *** If the user does not grant general consent, do not transmit user data.
  146. // In this sample application we terminate the application in this case.
  147. finish();
  148. } else if (type == DIALOG_TYPE_PRE_CONFIRMATION) {
  149. // *** POINT 4 *** If the user does not grant specific consent, do not transmit the correspon
  150. ding data.
  151. // The user did not grant consent, so we do nothing.
  152. }
  153. }
  154. private int getVersionCode() {
  155. int versionCode = -1;
  156. PackageManager packageManager = this.getPackageManager();
  157. try {
  158. PackageInfo packageInfo = packageManager.getPackageInfo(this.getPackageName(), PackageManager.GET_ACTIVITIES);
  159. versionCode = packageInfo.versionCode;
  160. } catch (NameNotFoundException e) {
  161. // This is sample, so omit the exception process
  162. }
  163. return versionCode;
  164. }
  165. @Override
  166. public boolean onCreateOptionsMenu(Menu menu) {
  167. getMenuInflater().inflate(R.menu.main, menu);
  168. return true;
  169. }
  170. @Override
  171. public boolean onOptionsItemSelected(MenuItem item) {
  172. switch (item.getItemId()) {
  173. case R.id.action_show_pp:
  174. // *** POINT 5 *** Provide methods by which the user can review the application privacy policy.
  175. Intent intent = new Intent();
  176. intent.setClass(this, WebViewAssetsActivity.class);
  177. startActivity(intent);
  178. return true;
  179. case R.id.action_del_id:
  180. // *** POINT 6 *** Provide methods by which transmitted data can be deleted by user operations.
  181. new SendDataAsyncTack().execute(DEL_ID_URI, UserId);
  182. return true;
  183. case R.id.action_donot_send_id:
  184. // *** POINT 7 *** Provide methods by which transmitting data can be stopped by user operations.
  185. // If the user stop sending data, user consent is deemed to have been revoked.
  186. SharedPreferences.Editor pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE).edit();
  187. pref.putInt(PRIVACY_POLICY_COMPREHENSIVE_AGREED_KEY, 0);
  188. pref.apply();
  189. // In this sample application if the user data cannot be sent by user operations,
  190. // finish the application because we do nothing.
  191. String message = getString(R.string.stopSendUserData);
  192. Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + message, Toast.LENGTH_SHORT).show();
  193. finish();
  194. return true;
  195. }
  196. return false;
  197. }
  198. @Override
  199. public void onConnected(Bundle connectionHint) {
  200. if (mLocationClient != null && mLocationClient.isConnected()) {
  201. Location currentLocation = mLocationClient.getLastLocation();
  202. if (currentLocation != null) {
  203. String locationData = "Latitude ¥t: " + currentLocation.getLatitude() + "¥n¥tLongitude ¥t: " + currentLocation.getLongitude();
  204. String text = "¥n" + getString(R.string.your_location_title) + "¥n¥t" + locationData;
  205. TextView appText = (TextView) findViewById(R.id.appText);
  206. appText.setText(text);
  207. }
  208. }
  209. }
  210. @Override
  211. public void onConnectionFailed(ConnectionResult result) {
  212. if (result.hasResolution()) {
  213. try {
  214. result.startResolutionForResult(this, CONNECTION_FAILURE_RESOLUTION_REQUEST);
  215. } catch (IntentSender.SendIntentException e) {
  216. e.printStackTrace();
  217. }
  218. }
  219. }
  220. @Override
  221. public void onDisconnected() {
  222. mLocationClient = null;
  223. }
  224. private class GetDataAsyncTask extends AsyncTask<String, Void, String> {
  225. private String extMessage = "";
  226. @Override
  227. protected String doInBackground(String... params) {
  228. // *** POINT 8 *** Use UUIDs or cookies to keep track of user data
  229. // In this sample we use an ID generated on the server side
  230. SharedPreferences sp = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE);
  231. UserId = sp.getString(ID_KEY, null);
  232. if (UserId == null) {
  233. // No token in SharedPreferences; fetch ID from server
  234. try {
  235. UserId = NetworkUtil.getCookie(GET_ID_URI, "", "id");
  236. } catch (IOException e) {
  237. // Catch exceptions such as certification errors
  238. extMessage = e.toString();
  239. }
  240. // Store the fetched ID in SharedPreferences
  241. sp.edit().putString(ID_KEY, UserId).commit();
  242. }
  243. return UserId;
  244. }
  245. @Override
  246. protected void onPostExecute(final String data) {
  247. String status = (data != null) ? "success" : "error";
  248. Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + status + " : " +
  249. extMessage, Toast.LENGTH_SHORT).show();
  250. }
  251. }
  252. private class SendDataAsyncTack extends AsyncTask<String, Void, Boolean> {
  253. private String extMessage = "";
  254. @Override
  255. protected Boolean doInBackground(String... params) {
  256. String url = params[0];
  257. String id = params[1];
  258. String location = params.length > 2 ? params[2] : null;
  259. String nickname = params.length > 3 ? params[3] : null;
  260. Boolean result = false;
  261. try {
  262. JSONObject jsonData = new JSONObject();
  263. jsonData.put(ID_KEY, id);
  264. if (location != null)
  265. jsonData.put(LOCATION_KEY, location);
  266. if (nickname != null)
  267. jsonData.put(NICK_NAME_KEY, nickname);
  268. NetworkUtil.sendJSON(url, "", jsonData.toString());
  269. result = true;
  270. } catch (IOException e) {
  271. // Catch exceptions such as certification errors
  272. extMessage = e.toString();
  273. } catch (JSONException e) {
  274. extMessage = e.toString();
  275. }
  276. return result;
  277. }
  278. @Override
  279. protected void onPostExecute(Boolean result) {
  280. String status = result ? "Success" : "Error";
  281. Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + status + " : " +
  282. extMessage, Toast.LENGTH_SHORT).show();
  283. }
  284. }
  285. }

ConfirmFragment.java

  1. package org.jssec.android.privacypolicy;
  2. import android.app.Activity;
  3. import android.app.AlertDialog;
  4. import android.app.Dialog;
  5. import android.content.Context;
  6. import android.content.DialogInterface;
  7. import android.content.Intent;
  8. import android.os.Bundle;
  9. import android.support.v4.app.DialogFragment;
  10. import android.view.LayoutInflater;
  11. import android.view.View;
  12. import android.view.View.OnClickListener;
  13. import android.widget.TextView;
  14. public class ConfirmFragment extends DialogFragment {
  15. private DialogListener mListener = null;
  16. public static interface DialogListener {
  17. public void onPositiveButtonClick(int type);
  18. public void onNegativeButtonClick(int type);
  19. }
  20. public static ConfirmFragment newInstance(int title, int sentence, int type) {
  21. ConfirmFragment fragment = new ConfirmFragment();
  22. Bundle args = new Bundle();
  23. args.putInt("title", title);
  24. args.putInt("sentence", sentence);
  25. args.putInt("type", type);
  26. fragment.setArguments(args);
  27. return fragment;
  28. }
  29. @Override
  30. public Dialog onCreateDialog(Bundle args) {
  31. // *** POINT 1 *** On first launch (or application update), obtain broad consent to transmit user data that will be handled by the application.
  32. // *** POINT 3 *** Obtain specific consent before transmitting user data that requires particularly delicate handling.
  33. final int title = getArguments().getInt("title");
  34. final int sentence = getArguments().getInt("sentence");
  35. final int type = getArguments().getInt("type");
  36. LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  37. View content = inflater.inflate(R.layout.fragment_comfirm, null);
  38. TextView linkPP = (TextView) content.findViewById(R.id.tx_link_pp);
  39. linkPP.setOnClickListener(new OnClickListener() {
  40. @Override
  41. public void onClick(View v) {
  42. // *** POINT 5 *** Provide methods by which the user can review the application privacy policy.
  43. Intent intent = new Intent();
  44. intent.setClass(getActivity(), WebViewAssetsActivity.class);
  45. startActivity(intent);
  46. }
  47. });
  48. AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
  49. builder.setIcon(R.drawable.ic_launcher);
  50. builder.setTitle(title);
  51. builder.setMessage(sentence);
  52. builder.setView(content);
  53. builder.setPositiveButton(R.string.buttonConsent, new DialogInterface.OnClickListener() {
  54. public void onClick(DialogInterface dialog, int whichButton) {
  55. if (mListener != null) {
  56. mListener.onPositiveButtonClick(type);
  57. }
  58. }
  59. });
  60. builder.setNegativeButton(R.string.buttonDonotConsent, new DialogInterface.OnClickListener() {
  61. public void onClick(DialogInterface dialog, int whichButton) {
  62. if (mListener != null) {
  63. mListener.onNegativeButtonClick(type);
  64. }
  65. }
  66. });
  67. Dialog dialog = builder.create();
  68. dialog.setCanceledOnTouchOutside(false);
  69. return dialog;
  70. }
  71. @Override
  72. public void onAttach(Activity activity) {
  73. super.onAttach(activity);
  74. if (!(activity instanceof DialogListener)) {
  75. throw new ClassCastException(activity.toString() + " must implement DialogListener.");
  76. }
  77. mListener = (DialogListener) activity;
  78. }
  79. public void setDialogListener(DialogListener listener) {
  80. mListener = listener;
  81. }
  82. }

WebViewAssetsActivity.java

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

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

要点:

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

MainActivity.java

  1. package org.jssec.android.privacypolicynopreconfirm;
  2. import java.io.IOException;
  3. import org.json.JSONException;
  4. import org.json.JSONObject;
  5. import org.jssec.android.privacypolicynopreconfirm.MainActivity;
  6. import org.jssec.android.privacypolicynopreconfirm.R;
  7. import org.jssec.android.privacypolicynopreconfirm.ConfirmFragment.DialogListener;
  8. import android.os.AsyncTask;
  9. import android.os.Bundle;
  10. import android.content.Intent;
  11. import android.content.SharedPreferences;
  12. import android.content.pm.PackageInfo;
  13. import android.content.pm.PackageManager;
  14. import android.content.pm.PackageManager.NameNotFoundException;
  15. import android.support.v4.app.FragmentActivity;
  16. import android.support.v4.app.FragmentManager;
  17. import android.telephony.TelephonyManager;
  18. import android.text.Editable;
  19. import android.text.TextWatcher;
  20. import android.view.Menu;
  21. import android.view.MenuItem;
  22. import android.view.View;
  23. import android.widget.TextView;
  24. import android.widget.Toast;
  25. public class MainActivity extends FragmentActivity implements DialogListener {
  26. private final String BASE_URL = "https://www.example.com/pp";
  27. private final String GET_ID_URI = BASE_URL + "/get_id.php";
  28. private final String SEND_DATA_URI = BASE_URL + "/send_data.php";
  29. private final String DEL_ID_URI = BASE_URL + "/del_id.php";
  30. private final String ID_KEY = "id";
  31. private final String NICK_NAME_KEY = "nickname";
  32. private final String IMEI_KEY = "imei";
  33. private final String PRIVACY_POLICY_AGREED_KEY = "privacyPolicyAgreed";
  34. private final String PRIVACY_POLICY_PREF_NAME = "privacypolicy_preference";
  35. private String UserId = "";
  36. private final int DIALOG_TYPE_COMPREHENSIVE_AGREEMENT = 1;
  37. private final int VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW = 1;
  38. private TextWatcher watchHandler = new TextWatcher() {
  39. @Override
  40. public void beforeTextChanged(CharSequence s, int start, int count, int after) {
  41. }
  42. @Override
  43. public void onTextChanged(CharSequence s, int start, int before, int count) {
  44. boolean buttonEnable = (s.length() > 0);
  45. MainActivity.this.findViewById(R.id.buttonStart).setEnabled(buttonEnable);
  46. }
  47. @Override
  48. public void afterTextChanged(Editable s) {
  49. }
  50. };
  51. @Override
  52. protected void onCreate(Bundle savedInstanceState) {
  53. super.onCreate(savedInstanceState);
  54. setContentView(R.layout.activity_main);
  55. // Fetch user ID from serverFetch user ID from server
  56. new GetDataAsyncTask().execute();
  57. findViewById(R.id.buttonStart).setEnabled(false);
  58. ((TextView) findViewById(R.id.editTextNickname)).addTextChangedListener(watchHandler);
  59. }
  60. @Override
  61. protected void onStart() {
  62. super.onStart();
  63. SharedPreferences pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE);
  64. int privacyPolicyAgreed = pref.getInt(PRIVACY_POLICY_AGREED_KEY, -1);
  65. if (privacyPolicyAgreed <= VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW) {
  66. // *** POINT 1 *** On first launch (or application update), obtain broad consent to transmit user data that will be handled by the application.
  67. // 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.
  68. ConfirmFragment dialog = ConfirmFragment.newInstance(R.string.privacyPolicy, R.string.agreePr
  69. ivacyPolicy, DIALOG_TYPE_COMPREHENSIVE_AGREEMENT);
  70. dialog.setDialogListener(this);
  71. FragmentManager fragmentManager = getSupportFragmentManager();
  72. dialog.show(fragmentManager, "dialog");
  73. }
  74. }
  75. public void onSendToServer(View view) {
  76. String nickname = ((TextView) findViewById(R.id.editTextNickname)).getText().toString();
  77. TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
  78. String imei = tm.getDeviceId();
  79. Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + "¥n - nickname : " + nickname + ", imei = " + imei, Toast.LENGTH_SHORT).show();
  80. new SendDataAsyncTack().execute(SEND_DATA_URI, UserId, nickname, imei);
  81. }
  82. public void onPositiveButtonClick(int type) {
  83. if (type == DIALOG_TYPE_COMPREHENSIVE_AGREEMENT) {
  84. // *** POINT 1 *** On first launch (or application update), obtain broad consent to transmit
  85. user data that will be handled by the application.
  86. SharedPreferences.Editor pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE).edit();
  87. pref.putInt(PRIVACY_POLICY_AGREED_KEY, getVersionCode());
  88. pref.apply();
  89. }
  90. }
  91. public void onNegativeButtonClick(int type) {
  92. if (type == DIALOG_TYPE_COMPREHENSIVE_AGREEMENT) {
  93. // *** POINT 2 *** If the user does not grant general consent, do not transmit user data.
  94. // In this sample application we terminate the application in this case.
  95. finish();
  96. }
  97. }
  98. private int getVersionCode() {
  99. int versionCode = -1;
  100. PackageManager packageManager = this.getPackageManager();
  101. try {
  102. PackageInfo packageInfo = packageManager.getPackageInfo(this.getPackageName(), PackageManager.GET_ACTIVITIES);
  103. versionCode = packageInfo.versionCode;
  104. } catch (NameNotFoundException e) {
  105. // This is sample, so omit the exception process
  106. }
  107. return versionCode;
  108. }
  109. @Override
  110. public boolean onCreateOptionsMenu(Menu menu) {
  111. getMenuInflater().inflate(R.menu.main, menu);
  112. return true;
  113. }
  114. @Override
  115. public boolean onOptionsItemSelected(MenuItem item) {
  116. switch (item.getItemId()) {
  117. case R.id.action_show_pp:
  118. // *** POINT 3 *** Provide methods by which the user can review the application privacy policy.
  119. Intent intent = new Intent();
  120. intent.setClass(this, WebViewAssetsActivity.class);
  121. startActivity(intent);
  122. return true;
  123. case R.id.action_del_id:
  124. // *** POINT 4 *** Provide methods by which transmitted data can be deleted by user operation
  125. s.
  126. new SendDataAsyncTack().execute(DEL_ID_URI, UserId);
  127. return true;
  128. case R.id.action_donot_send_id:
  129. // *** POINT 5 *** Provide methods by which transmitting data can be stopped by user operations.
  130. // If the user stop sending data, user consent is deemed to have been revoked.
  131. SharedPreferences.Editor pref = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE).edit();
  132. pref.putInt(PRIVACY_POLICY_AGREED_KEY, 0);
  133. pref.apply();
  134. // In this sample application if the user data cannot be sent by user operations,
  135. // finish the application because we do nothing.
  136. String message = getString(R.string.stopSendUserData);
  137. Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + message, Toast.L
  138. ENGTH_SHORT).show();
  139. finish();
  140. return true;
  141. }
  142. return false;
  143. }
  144. private class GetDataAsyncTask extends AsyncTask<String, Void, String> {
  145. private String extMessage = "";
  146. @Override
  147. protected String doInBackground(String... params) {
  148. // *** POINT 6 *** Use UUIDs or cookies to keep track of user data
  149. // In this sample we use an ID generated on the server side
  150. SharedPreferences sp = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE);
  151. UserId = sp.getString(ID_KEY, null);
  152. if (UserId == null) {
  153. // No token in SharedPreferences; fetch ID from server
  154. try {
  155. UserId = NetworkUtil.getCookie(GET_ID_URI, "", "id");
  156. } catch (IOException e) {
  157. // Catch exceptions such as certification errors
  158. extMessage = e.toString();
  159. }
  160. // Store the fetched ID in SharedPreferences
  161. sp.edit().putString(ID_KEY, UserId).commit();
  162. }
  163. return UserId;
  164. }
  165. @Override
  166. protected void onPostExecute(final String data) {
  167. String status = (data != null) ? "success" : "error";
  168. Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + status + " : " +
  169. extMessage, Toast.LENGTH_SHORT).show();
  170. }
  171. }
  172. private class SendDataAsyncTack extends AsyncTask<String, Void, Boolean> {
  173. private String extMessage = "";
  174. @Override
  175. protected Boolean doInBackground(String... params) {
  176. String url = params[0];
  177. String id = params[1];
  178. String nickname = params.length > 2 ? params[2] : null;
  179. String imei = params.length > 3 ? params[3] : null;
  180. Boolean result = false;
  181. try {
  182. JSONObject jsonData = new JSONObject();
  183. jsonData.put(ID_KEY, id);
  184. if (nickname != null)
  185. jsonData.put(NICK_NAME_KEY, nickname);
  186. if (imei != null)
  187. jsonData.put(IMEI_KEY, imei);
  188. NetworkUtil.sendJSON(url, "", jsonData.toString());
  189. result = true;
  190. } catch (IOException e) {
  191. // Catch exceptions such as certification errors
  192. extMessage = e.toString();
  193. } catch (JSONException e) {
  194. extMessage = e.toString();
  195. }
  196. return result;
  197. }
  198. @Override
  199. protected void onPostExecute(Boolean result) {
  200. String status = result ? "Success" : "Error";
  201. Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + status + " : " +
  202. extMessage, Toast.LENGTH_SHORT).show();
  203. }
  204. }
  205. }

ConfirmFragment.java

  1. package org.jssec.android.privacypolicynopreconfirm;
  2. import android.app.Activity;
  3. import android.app.AlertDialog;
  4. import android.app.Dialog;
  5. import android.content.Context;
  6. import android.content.DialogInterface;
  7. import android.content.Intent;
  8. import android.os.Bundle;
  9. import android.support.v4.app.DialogFragment;
  10. import android.view.LayoutInflater;
  11. import android.view.View;
  12. import android.view.View.OnClickListener;
  13. import android.widget.TextView;
  14. public class ConfirmFragment extends DialogFragment {
  15. private DialogListener mListener = null;
  16. public static interface DialogListener {
  17. public void onPositiveButtonClick(int type);
  18. public void onNegativeButtonClick(int type);
  19. }
  20. public static ConfirmFragment newInstance(int title, int sentence, int type) {
  21. ConfirmFragment fragment = new ConfirmFragment();
  22. Bundle args = new Bundle();
  23. args.putInt("title", title);
  24. args.putInt("sentence", sentence);
  25. args.putInt("type", type);
  26. fragment.setArguments(args);
  27. return fragment;
  28. }
  29. @Override
  30. public Dialog onCreateDialog(Bundle args) {
  31. // *** POINT 1 *** On first launch (or application update), obtain broad consent to transmit user data that will be handled by the application.
  32. final int title = getArguments().getInt("title");
  33. final int sentence = getArguments().getInt("sentence");
  34. final int type = getArguments().getInt("type");
  35. LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  36. View content = inflater.inflate(R.layout.fragment_comfirm, null);
  37. TextView linkPP = (TextView) content.findViewById(R.id.tx_link_pp);
  38. linkPP.setOnClickListener(new OnClickListener() {
  39. @Override
  40. public void onClick(View v) {
  41. // *** POINT 3 *** Provide methods by which the user can review the application privacy policy.
  42. Intent intent = new Intent();
  43. intent.setClass(getActivity(), WebViewAssetsActivity.class);
  44. startActivity(intent);
  45. }
  46. });
  47. AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
  48. builder.setIcon(R.drawable.ic_launcher);
  49. builder.setTitle(title);
  50. builder.setMessage(sentence);
  51. builder.setView(content);
  52. builder.setPositiveButton(R.string.buttonConsent, new DialogInterface.OnClickListener() {
  53. public void onClick(DialogInterface dialog, int whichButton) {
  54. if (mListener != null) {
  55. mListener.onPositiveButtonClick(type);
  56. }
  57. }
  58. });
  59. builder.setNegativeButton(R.string.buttonDonotConsent, new DialogInterface.OnClickListener() {
  60. public void onClick(DialogInterface dialog, int whichButton) {
  61. if (mListener != null) {
  62. mListener.onNegativeButtonClick(type);
  63. }
  64. }
  65. });
  66. Dialog dialog = builder.create();
  67. dialog.setCanceledOnTouchOutside(false);
  68. return dialog;
  69. }
  70. @Override
  71. public void onAttach(Activity activity) {
  72. super.onAttach(activity);
  73. if (!(activity instanceof DialogListener)) {
  74. throw new ClassCastException(activity.toString() + " must implement DialogListener.");
  75. }
  76. mListener = (DialogListener) activity;
  77. }
  78. public void setDialogListener(DialogListener listener) {
  79. mListener = listener;
  80. }
  81. }

WebViewAssetsActivity.java

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

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

要点:

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

MainActivity.java

  1. package org.jssec.android.privacypolicynocomprehensive;
  2. import java.io.IOException;
  3. import org.json.JSONException;
  4. import org.json.JSONObject;
  5. import android.os.AsyncTask;
  6. import android.os.Bundle;
  7. import android.content.Intent;
  8. import android.content.SharedPreferences;
  9. import android.support.v4.app.FragmentActivity;
  10. import android.text.Editable;
  11. import android.text.TextWatcher;
  12. import android.view.Menu;
  13. import android.view.MenuItem;
  14. import android.view.View;
  15. import android.widget.TextView;
  16. import android.widget.Toast;
  17. public class MainActivity extends FragmentActivity {
  18. private static final String BASE_URL = "https://www.example.com/pp";
  19. private static final String GET_ID_URI = BASE_URL + "/get_id.php";
  20. private static final String SEND_DATA_URI = BASE_URL + "/send_data.php";
  21. private static final String DEL_ID_URI = BASE_URL + "/del_id.php";
  22. private static final String ID_KEY = "id";
  23. private static final String NICK_NAME_KEY = "nickname";
  24. private static final String PRIVACY_POLICY_PREF_NAME = "privacypolicy_preference";
  25. private String UserId = "";
  26. private TextWatcher watchHandler = new TextWatcher() {
  27. @Override
  28. public void beforeTextChanged(CharSequence s, int start, int count, int after) {
  29. }
  30. @Override
  31. public void onTextChanged(CharSequence s, int start, int before, int count) {
  32. boolean buttonEnable = (s.length() > 0);
  33. MainActivity.this.findViewById(R.id.buttonStart).setEnabled(buttonEnable);
  34. }
  35. @Override
  36. public void afterTextChanged(Editable s) {
  37. }
  38. };
  39. @Override
  40. protected void onCreate(Bundle savedInstanceState) {
  41. super.onCreate(savedInstanceState);
  42. setContentView(R.layout.activity_main);
  43. // Fetch user ID from serverFetch user ID from server
  44. new GetDataAsyncTask().execute();
  45. findViewById(R.id.buttonStart).setEnabled(false);
  46. ((TextView) findViewById(R.id.editTextNickname)).addTextChangedListener(watchHandler);
  47. }
  48. public void onSendToServer(View view) {
  49. String nickname = ((TextView) findViewById(R.id.editTextNickname)).getText().toString();
  50. Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + "¥n - nickname : " + nickname, Toast.LENGTH_SHORT).show();
  51. new sendDataAsyncTack().execute(SEND_DATA_URI, UserId, nickname);
  52. }
  53. @Override
  54. public boolean onCreateOptionsMenu(Menu menu) {
  55. getMenuInflater().inflate(R.menu.main, menu);
  56. return true;
  57. }
  58. @Override
  59. public boolean onOptionsItemSelected(MenuItem item) {
  60. switch (item.getItemId()) {
  61. case R.id.action_show_pp:
  62. // *** POINT 1 *** Provide methods by which the user can review the application privacy policy.
  63. Intent intent = new Intent();
  64. intent.setClass(this, WebViewAssetsActivity.class);
  65. startActivity(intent);
  66. return true;
  67. case R.id.action_del_id:
  68. // *** POINT 2 *** Provide methods by which transmitted data can be deleted by user operations.
  69. new sendDataAsyncTack().execute(DEL_ID_URI, UserId);
  70. return true;
  71. case R.id.action_donot_send_id:
  72. // *** POINT 3 *** Provide methods by which transmitting data can be stopped by user operations.
  73. // In this sample application if the user data cannot be sent by user operations,
  74. // finish the application because we do nothing.
  75. String message = getString(R.string.stopSendUserData);
  76. Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + message, Toast.LENGTH_SHORT).show();
  77. finish();
  78. return true;
  79. }
  80. return false;
  81. }
  82. private class GetDataAsyncTask extends AsyncTask<String, Void, String> {
  83. private String extMessage = "";
  84. @Override
  85. protected String doInBackground(String... params) {
  86. // *** POINT 4 *** Use UUIDs or cookies to keep track of user data
  87. // In this sample we use an ID generated on the server side
  88. SharedPreferences sp = getSharedPreferences(PRIVACY_POLICY_PREF_NAME, MODE_PRIVATE);
  89. UserId = sp.getString(ID_KEY, null);
  90. if (UserId == null) {
  91. // No token in SharedPreferences; fetch ID from server
  92. try {
  93. UserId = NetworkUtil.getCookie(GET_ID_URI, "", "id");
  94. } catch (IOException e) {
  95. // Catch exceptions such as certification errors
  96. extMessage = e.toString();
  97. }
  98. // Store the fetched ID in SharedPreferences
  99. sp.edit().putString(ID_KEY, UserId).commit();
  100. }
  101. return UserId;
  102. }
  103. @Override
  104. protected void onPostExecute(final String data) {
  105. String status = (data != null) ? "success" : "error";
  106. Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + status + " : " +
  107. extMessage, Toast.LENGTH_SHORT).show();
  108. }
  109. }
  110. private class sendDataAsyncTack extends AsyncTask<String, Void, Boolean> {
  111. private String extMessage = "";
  112. @Override
  113. protected Boolean doInBackground(String... params) {
  114. String url = params[0];
  115. String id = params[1];
  116. String nickname = params.length > 2 ? params[2] : null;
  117. Boolean result = false;
  118. try {
  119. JSONObject jsonData = new JSONObject();
  120. jsonData.put(ID_KEY, id);
  121. if (nickname != null)
  122. jsonData.put(NICK_NAME_KEY, nickname);
  123. NetworkUtil.sendJSON(url, "", jsonData.toString());
  124. result = true;
  125. } catch (IOException e) {
  126. // Catch exceptions such as certification errors
  127. extMessage = e.toString();
  128. } catch (JSONException e) {
  129. extMessage = e.toString();
  130. }
  131. return result;
  132. }
  133. @Override
  134. protected void onPostExecute(Boolean result) {
  135. String status = result ? "Success" : "Error";
  136. Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + " - " + status + " : " +
  137. extMessage, Toast.LENGTH_SHORT).show();
  138. }
  139. }
  140. }

WebViewAssetsActivity.java

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

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

要点:

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

MainActivity.java

  1. package org.jssec.android.privacypolicynoinfosent;
  2. import com.google.android.gms.common.ConnectionResult;
  3. import com.google.android.gms.common.GooglePlayServicesClient;
  4. import com.google.android.gms.location.LocationClient;
  5. import android.location.Location;
  6. import android.net.Uri;
  7. import android.os.Bundle;
  8. import android.content.Intent;
  9. import android.content.IntentSender;
  10. import android.support.v4.app.FragmentActivity;
  11. import android.view.Menu;
  12. import android.view.View;
  13. import android.widget.TextView;
  14. import android.widget.Toast;
  15. public class MainActivity extends FragmentActivity implements GooglePlayServicesClient.ConnectionCallbacks,
  16. GooglePlayServicesClient.OnConnectionFailedListener {
  17. private LocationClient mLocationClient = null;
  18. private final int CONNECTION_FAILURE_RESOLUTION_REQUEST = 257;
  19. @Override
  20. protected void onCreate(Bundle savedInstanceState) {
  21. super.onCreate(savedInstanceState);
  22. setContentView(R.layout.activity_main);
  23. mLocationClient = new LocationClient(this, this, this);
  24. }
  25. @Override
  26. protected void onStart() {
  27. super.onStart();
  28. // Used to obtain location data
  29. if (mLocationClient != null) {
  30. mLocationClient.connect();
  31. }
  32. }
  33. @Override
  34. protected void onStop() {
  35. if (mLocationClient != null) {
  36. mLocationClient.disconnect();
  37. }
  38. super.onStop();
  39. }
  40. @Override
  41. public boolean onCreateOptionsMenu(Menu menu) {
  42. getMenuInflater().inflate(R.menu.main, menu);
  43. return true;
  44. }
  45. public void onStartMap(View view) {
  46. // *** POINT 1 *** You do not need to display an application privacy policy if your application w
  47. ill only use the information it obtains within the device.
  48. if (mLocationClient != null && mLocationClient.isConnected()) {
  49. Location currentLocation = mLocationClient.getLastLocation();
  50. if (currentLocation != null) {
  51. Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("geo:" + currentLocation.getLatitude() + "," + currentLocation.getLongitude()));
  52. startActivity(intent);
  53. }
  54. }
  55. }
  56. @Override
  57. public void onConnected(Bundle connectionHint) {
  58. if (mLocationClient != null && mLocationClient.isConnected()) {
  59. Location currentLocation = mLocationClient.getLastLocation();
  60. if (currentLocation != null) {
  61. String locationData = "Latitude ¥t: " + currentLocation.getLatitude() + "¥n¥tLongitude ¥t: " + currentLocation.getLongitude();
  62. String text = "¥n" + getString(R.string.your_location_title) + "¥n¥t" + locationData;
  63. Toast.makeText(MainActivity.this, this.getClass().getSimpleName() + text, Toast.LENGTH_SHORT).show();
  64. TextView appText = (TextView) findViewById(R.id.appText);
  65. appText.setText(text);
  66. }
  67. }
  68. }
  69. @Override
  70. public void onConnectionFailed(ConnectionResult result) {
  71. if (result.hasResolution()) {
  72. try {
  73. result.startResolutionForResult(this, CONNECTION_FAILURE_RESOLUTION_REQUEST);
  74. } catch (IntentSender.SendIntentException e) {
  75. e.printStackTrace();
  76. }
  77. }
  78. }
  79. @Override
  80. public void onDisconnected() {
  81. mLocationClient = null;
  82. Toast.makeText(this, "Disconnected. Please re-connect.", Toast.LENGTH_SHORT).show();
  83. }
  84. }

市场上的示例如下。

5.5.1.md - 图3