Java tutorial
package org.thoughtland.xlocation; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import javax.net.ssl.SSLException; import javax.xml.parsers.SAXParserFactory; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ConnectTimeoutException; import org.apache.http.conn.HttpHostConnectException; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.json.JSONArray; import org.json.JSONObject; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; import org.xmlpull.v1.XmlSerializer; import android.accounts.Account; import android.accounts.AccountManager; import android.annotation.SuppressLint; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.ProgressDialog; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.database.Cursor; import android.graphics.Color; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.PowerManager; import android.os.Process; import android.provider.ContactsContract; import android.provider.Settings.Secure; import android.support.v4.app.NotificationCompat; import android.text.TextUtils; import android.util.Log; import android.util.Patterns; import android.util.SparseArray; import android.util.Xml; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.CheckBox; import android.widget.ImageView; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.ScrollView; import android.widget.Spinner; import android.widget.RadioGroup.OnCheckedChangeListener; import android.widget.TextView; import android.widget.EditText; import android.widget.Toast; public class ActivityShare extends ActivityBase { private int mActionId; private AppListAdapter mAppAdapter; private SparseArray<AppState> mAppsByUid; private boolean mRunning = false; private boolean mAbort = false; private int mProgressCurrent; private int mProgressWidth = 0; private String mFileName = null; private boolean mInteractive = false; private static final int STATE_WAITING = 0; private static final int STATE_RUNNING = 1; private static final int STATE_SUCCESS = 2; private static final int STATE_FAILURE = 3; private static final int ACTIVITY_IMPORT_SELECT = 0; public static final String cUidList = "UidList"; public static final String cRestriction = "Restriction"; public static final String cInteractive = "Interactive"; public static final String cChoice = "Choice"; public static final String cFileName = "FileName"; public static final String HTTP_BASE_URL = "http://crowd.xlocation.eu/"; public static final String HTTPS_BASE_URL = "https://crowd.xlocation.eu/"; public static final int cSubmitLimit = 10; public static final int cProtocolVersion = 4; public static final String ACTION_EXPORT = "org.thoughtland.xlocation.action.EXPORT"; public static final String ACTION_IMPORT = "org.thoughtland.xlocation.action.IMPORT"; public static final String ACTION_FETCH = "org.thoughtland.xlocation.action.FETCH"; public static final String ACTION_SUBMIT = "org.thoughtland.xlocation.action.SUBMIT"; public static final String ACTION_TOGGLE = "org.thoughtland.xlocation.action.TOGGLE"; public static final int CHOICE_CLEAR = 1; public static final int CHOICE_TEMPLATE = 2; public static final int TIMEOUT_MILLISEC = 45000; private static ExecutorService mExecutor = Executors .newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new PriorityThreadFactory()); private static class PriorityThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setPriority(Thread.NORM_PRIORITY); return t; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Check privacy service client if (!PrivacyService.checkClient()) return; // Get data int userId = Util.getUserId(Process.myUid()); final Bundle extras = getIntent().getExtras(); final String action = getIntent().getAction(); final int[] uids = (extras != null && extras.containsKey(cUidList) ? extras.getIntArray(cUidList) : new int[0]); final String restrictionName = (extras != null ? extras.getString(cRestriction) : null); int choice = (extras != null && extras.containsKey(cChoice) ? extras.getInt(cChoice) : -1); if (action.equals(ACTION_EXPORT)) mFileName = (extras != null && extras.containsKey(cFileName) ? extras.getString(cFileName) : null); // License check if (action.equals(ACTION_IMPORT) || action.equals(ACTION_EXPORT)) { if (!Util.isProEnabled() && Util.hasProLicense(this) == null) { Util.viewUri(this, ActivityMain.cProUri); finish(); return; } } else if (action.equals(ACTION_FETCH) || (action.equals(ACTION_TOGGLE) && uids.length > 1)) { if (Util.hasProLicense(this) == null) { Util.viewUri(this, ActivityMain.cProUri); finish(); return; } } // Registration check if (action.equals(ACTION_SUBMIT) && !registerDevice(this)) { finish(); return; } // Check whether we need a user interface if (extras != null && extras.containsKey(cInteractive) && extras.getBoolean(cInteractive, false)) mInteractive = true; // Set layout setContentView(R.layout.sharelist); // Reference controls final TextView tvDescription = (TextView) findViewById(R.id.tvDescription); final ScrollView svToggle = (ScrollView) findViewById(R.id.svToggle); final RadioGroup rgToggle = (RadioGroup) findViewById(R.id.rgToggle); final Spinner spRestriction = (Spinner) findViewById(R.id.spRestriction); RadioButton rbClear = (RadioButton) findViewById(R.id.rbClear); RadioButton rbTemplateFull = (RadioButton) findViewById(R.id.rbTemplateFull); RadioButton rbODEnable = (RadioButton) findViewById(R.id.rbEnableOndemand); RadioButton rbODDisable = (RadioButton) findViewById(R.id.rbDisableOndemand); final Spinner spTemplate = (Spinner) findViewById(R.id.spTemplate); final CheckBox cbClear = (CheckBox) findViewById(R.id.cbClear); final Button btnOk = (Button) findViewById(R.id.btnOk); final Button btnCancel = (Button) findViewById(R.id.btnCancel); // Set title if (action.equals(ACTION_TOGGLE)) { mActionId = R.string.menu_toggle; setTitle(R.string.menu_toggle); } else if (action.equals(ACTION_IMPORT)) { mActionId = R.string.menu_import; setTitle(R.string.menu_import); } else if (action.equals(ACTION_EXPORT)) { mActionId = R.string.menu_export; setTitle(R.string.menu_export); } else if (action.equals(ACTION_FETCH)) { mActionId = R.string.menu_fetch; setTitle(R.string.menu_fetch); } else if (action.equals(ACTION_SUBMIT)) { mActionId = R.string.menu_submit; setTitle(R.string.menu_submit); } else { finish(); return; } // Get localized restriction name List<String> listRestrictionName = new ArrayList<String>( PrivacyManager.getRestrictions(this).navigableKeySet()); listRestrictionName.add(0, getString(R.string.menu_all)); // Build restriction adapter SpinnerAdapter saRestriction = new SpinnerAdapter(this, android.R.layout.simple_spinner_item); saRestriction.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); saRestriction.addAll(listRestrictionName); // Setup restriction spinner int pos = 0; if (restrictionName != null) for (String restriction : PrivacyManager.getRestrictions(this).values()) { pos++; if (restrictionName.equals(restriction)) break; } spRestriction.setAdapter(saRestriction); spRestriction.setSelection(pos); // Build template adapter SpinnerAdapter spAdapter = new SpinnerAdapter(this, android.R.layout.simple_spinner_item); spAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spAdapter.add(getString(R.string.title_default)); for (int i = 1; i <= 4; i++) spAdapter.add(getString(R.string.title_alternate) + " " + i); spTemplate.setAdapter(spAdapter); // Build application list AppListTask appListTask = new AppListTask(); appListTask.executeOnExecutor(mExecutor, uids); // Import/export filename if (action.equals(ACTION_EXPORT) || action.equals(ACTION_IMPORT)) { // Check for availability of sharing intent Intent file = new Intent(Intent.ACTION_GET_CONTENT); file.setType("file/*"); boolean hasIntent = Util.isIntentAvailable(ActivityShare.this, file); // Get file name if (mFileName == null) if (action.equals(ACTION_EXPORT)) { String packageName = null; if (uids.length == 1) try { ApplicationInfoEx appInfo = new ApplicationInfoEx(this, uids[0]); packageName = appInfo.getPackageName().get(0); } catch (Throwable ex) { Util.bug(null, ex); } mFileName = getFileName(this, hasIntent, packageName); } else mFileName = (hasIntent ? null : getFileName(this, false, null)); if (mFileName == null) fileChooser(); else showFileName(); if (action.equals(ACTION_IMPORT)) cbClear.setVisibility(View.VISIBLE); } else if (action.equals(ACTION_FETCH)) { tvDescription.setText(getBaseURL()); cbClear.setVisibility(View.VISIBLE); } else if (action.equals(ACTION_TOGGLE)) { tvDescription.setText(R.string.menu_toggle); spRestriction.setVisibility(View.VISIBLE); svToggle.setVisibility(View.VISIBLE); // Listen for radio button rgToggle.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { btnOk.setEnabled(checkedId >= 0); spRestriction.setVisibility( checkedId == R.id.rbEnableOndemand || checkedId == R.id.rbDisableOndemand ? View.GONE : View.VISIBLE); spTemplate.setVisibility(checkedId == R.id.rbTemplateCategory || checkedId == R.id.rbTemplateFull || checkedId == R.id.rbTemplateMergeSet || checkedId == R.id.rbTemplateMergeReset ? View.VISIBLE : View.GONE); } }); boolean ondemand = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingOnDemand, true); rbODEnable.setVisibility(ondemand ? View.VISIBLE : View.GONE); rbODDisable.setVisibility(ondemand ? View.VISIBLE : View.GONE); if (choice == CHOICE_CLEAR) rbClear.setChecked(true); else if (choice == CHOICE_TEMPLATE) rbTemplateFull.setChecked(true); } else tvDescription.setText(getBaseURL()); if (mInteractive) { // Enable ok // (showFileName does this for export/import) if (action.equals(ACTION_SUBMIT) || action.equals(ACTION_FETCH)) btnOk.setEnabled(true); // Listen for ok btnOk.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { btnOk.setEnabled(false); // Toggle if (action.equals(ACTION_TOGGLE)) { mRunning = true; for (int i = 0; i < rgToggle.getChildCount(); i++) ((RadioButton) rgToggle.getChildAt(i)).setEnabled(false); int pos = spRestriction.getSelectedItemPosition(); String restrictionName = (pos == 0 ? null : (String) PrivacyManager.getRestrictions(ActivityShare.this).values().toArray()[pos - 1]); new ToggleTask().executeOnExecutor(mExecutor, restrictionName); // Import } else if (action.equals(ACTION_IMPORT)) { mRunning = true; cbClear.setEnabled(false); new ImportTask().executeOnExecutor(mExecutor, new File(mFileName), cbClear.isChecked()); } // Export else if (action.equals(ACTION_EXPORT)) { mRunning = true; new ExportTask().executeOnExecutor(mExecutor, new File(mFileName)); // Fetch } else if (action.equals(ACTION_FETCH)) { if (uids.length > 0) { mRunning = true; cbClear.setEnabled(false); new FetchTask().executeOnExecutor(mExecutor, cbClear.isChecked()); } } // Submit else if (action.equals(ACTION_SUBMIT)) { if (uids.length > 0) { if (uids.length <= cSubmitLimit) { mRunning = true; new SubmitTask().executeOnExecutor(mExecutor); } else { String message = getString(R.string.msg_limit, cSubmitLimit + 1); Toast.makeText(ActivityShare.this, message, Toast.LENGTH_LONG).show(); btnOk.setEnabled(false); } } } } }); } else btnOk.setEnabled(false); // Listen for cancel btnCancel.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { if (mRunning) { mAbort = true; Toast.makeText(ActivityShare.this, getString(R.string.msg_abort), Toast.LENGTH_LONG).show(); } else finish(); } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent dataIntent) { super.onActivityResult(requestCode, resultCode, dataIntent); // Import select if (requestCode == ACTIVITY_IMPORT_SELECT) if (resultCode == RESULT_CANCELED || dataIntent == null) finish(); else { String fileName = dataIntent.getData().getPath(); mFileName = fileName.replace("/document/primary:", Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar); showFileName(); } } // State management public void setState(int uid, int state, String message) { final AppState app = mAppsByUid.get(uid); app.message = message; app.state = state; runOnUiThread(new Runnable() { @Override public void run() { if (mAppAdapter != null) { mAppAdapter.notifyDataSetChanged(); int position = mAppAdapter.getPosition(app); if (position >= 0) { ListView lvShare = (ListView) findViewById(R.id.lvShare); lvShare.smoothScrollToPosition(position); } } } }); } public void setState(int uid, int state) { AppState app = mAppsByUid.get(uid); app.state = state; } public void setMessage(int uid, String message) { AppState app = mAppsByUid.get(uid); app.message = message; } // App info and share state private class AppState implements Comparable<AppState> { public int state = STATE_WAITING; public String message = null; public ApplicationInfoEx appInfo; public AppState(int uid) { appInfo = new ApplicationInfoEx(ActivityShare.this, uid); } @Override public int compareTo(AppState other) { return this.appInfo.compareTo(other.appInfo); } } // Adapters private class SpinnerAdapter extends ArrayAdapter<String> { public SpinnerAdapter(Context context, int textViewResourceId) { super(context, textViewResourceId); } } private class AppListAdapter extends ArrayAdapter<AppState> { private LayoutInflater mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); public AppListAdapter(Context context, int resource, List<AppState> objects) { super(context, resource, objects); } public List<Integer> getListUid() { List<Integer> uids = new ArrayList<Integer>(); for (int i = 0; i < this.getCount(); i++) uids.add(this.getItem(i).appInfo.getUid()); return uids; } public List<ApplicationInfoEx> getListAppInfo() { List<ApplicationInfoEx> apps = new ArrayList<ApplicationInfoEx>(); for (int i = 0; i < this.getCount(); i++) apps.add(this.getItem(i).appInfo); return apps; } private class ViewHolder { private View row; private int position; public ImageView imgIcon; public ImageView imgInfo; public TextView tvName; public ImageView imgResult; public ProgressBar pbRunning; public TextView tvMessage; public ViewHolder(View theRow, int thePosition) { row = theRow; position = thePosition; imgIcon = (ImageView) row.findViewById(R.id.imgIcon); imgInfo = (ImageView) row.findViewById(R.id.imgInfo); tvName = (TextView) row.findViewById(R.id.tvApp); imgResult = (ImageView) row.findViewById(R.id.imgResult); pbRunning = (ProgressBar) row.findViewById(R.id.pbRunning); tvMessage = (TextView) row.findViewById(R.id.tvMessage); } } @Override @SuppressLint("InflateParams") public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = mInflater.inflate(R.layout.shareentry, null); holder = new ViewHolder(convertView, position); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); holder.position = position; } // Get info final AppState xApp = getItem(holder.position); // Set background color if (xApp.appInfo.isSystem()) holder.row.setBackgroundColor(getResources().getColor(getThemed(R.attr.color_dangerous))); else holder.row.setBackgroundColor(Color.TRANSPARENT); // Display icon holder.imgIcon.setImageDrawable(xApp.appInfo.getIcon(ActivityShare.this)); holder.imgIcon.setVisibility(View.VISIBLE); holder.imgInfo.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // Packages can be selected on the web site Util.viewUri(ActivityShare.this, Uri.parse(String.format(getBaseURL() + "?package_name=%s", xApp.appInfo.getPackageName().get(0)))); } }); // Set app name holder.tvName.setText(xApp.appInfo.toString()); // Show app share state if (TextUtils.isEmpty(xApp.message)) holder.tvMessage.setVisibility(View.GONE); else { holder.tvMessage.setVisibility(View.VISIBLE); holder.tvMessage.setText(xApp.message); } switch (xApp.state) { case STATE_WAITING: holder.imgResult.setVisibility(View.GONE); holder.pbRunning.setVisibility(View.GONE); break; case STATE_RUNNING: holder.imgResult.setVisibility(View.GONE); holder.pbRunning.setVisibility(View.VISIBLE); break; case STATE_SUCCESS: holder.imgResult.setBackgroundResource(R.drawable.btn_check_buttonless_on); holder.imgResult.setVisibility(View.VISIBLE); holder.pbRunning.setVisibility(View.GONE); break; case STATE_FAILURE: holder.imgResult.setBackgroundResource(R.drawable.indicator_input_error); holder.imgResult.setVisibility(View.VISIBLE); holder.pbRunning.setVisibility(View.GONE); break; default: Util.log(null, Log.ERROR, "Unknown state=" + xApp.state); break; } return convertView; } } // Tasks private class AppListTask extends AsyncTask<int[], Object, List<AppState>> { private ProgressDialog mProgressDialog; @Override protected List<AppState> doInBackground(int[]... params) { int[] uids = params[0]; List<AppState> apps = new ArrayList<AppState>(); mAppsByUid = new SparseArray<AppState>(); if (!mInteractive && mActionId == R.string.menu_export) { // Build list of distinct uids for export List<Integer> listUid = new ArrayList<Integer>(); for (PackageInfo pInfo : getPackageManager().getInstalledPackages(0)) if (!listUid.contains(pInfo.applicationInfo.uid)) listUid.add(pInfo.applicationInfo.uid); // Convert to primitive array uids = new int[listUid.size()]; for (int i = 0; i < listUid.size(); i++) uids[i] = listUid.get(i); } mProgressDialog.setMax(uids.length); for (int i = 0; i < uids.length; i++) { mProgressDialog.setProgress(i); AppState app = new AppState(uids[i]); apps.add(app); mAppsByUid.put(uids[i], app); } Collections.sort(apps); return apps; } @Override protected void onPreExecute() { super.onPreExecute(); // Show progress dialog mProgressDialog = new ProgressDialog(ActivityShare.this); mProgressDialog.setMessage(getString(R.string.msg_loading)); mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); mProgressDialog.setProgressNumberFormat(null); mProgressDialog.setCancelable(false); mProgressDialog.setCanceledOnTouchOutside(false); mProgressDialog.show(); } @Override protected void onPostExecute(List<AppState> listApp) { if (!ActivityShare.this.isFinishing()) { // Display app list mAppAdapter = new AppListAdapter(ActivityShare.this, R.layout.shareentry, listApp); ListView lvShare = (ListView) findViewById(R.id.lvShare); lvShare.setAdapter(mAppAdapter); // Dismiss progress dialog if (mProgressDialog.isShowing()) try { mProgressDialog.dismiss(); } catch (IllegalArgumentException ignored) { } // Launch non-interactive export if (!mInteractive && mActionId == R.string.menu_export) { mRunning = true; new ExportTask().executeOnExecutor(mExecutor, new File(mFileName)); } } super.onPostExecute(listApp); } } private class ToggleTask extends AsyncTask<String, Integer, Throwable> { @Override protected Throwable doInBackground(String... params) { // Get wakelock PowerManager pm = (PowerManager) ActivityShare.this.getSystemService(Context.POWER_SERVICE); PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "XLocation.Toggle"); wl.acquire(); try { // Get data mProgressCurrent = 0; List<Integer> lstUid = mAppAdapter.getListUid(); final String restrictionName = params[0]; int actionId = ((RadioGroup) ActivityShare.this.findViewById(R.id.rgToggle)) .getCheckedRadioButtonId(); Spinner spTemplate = ((Spinner) ActivityShare.this.findViewById(R.id.spTemplate)); String templateName = Meta.cTypeTemplate; if (spTemplate.getSelectedItemPosition() > 0) templateName = Meta.cTypeTemplate + spTemplate.getSelectedItemPosition(); for (Integer uid : lstUid) try { if (mAbort) throw new AbortException(ActivityShare.this); // Update progess publishProgress(++mProgressCurrent, lstUid.size() + 1); setState(uid, STATE_RUNNING, null); List<Boolean> oldState = PrivacyManager.getRestartStates(uid, restrictionName); if (actionId == R.id.rbClear) { PrivacyManager.deleteRestrictions(uid, restrictionName, (restrictionName == null)); if (restrictionName == null) { PrivacyManager.deleteUsage(uid); PrivacyManager.deleteSettings(uid); } } else if (actionId == R.id.rbRestrict) { PrivacyManager.setRestriction(uid, restrictionName, null, true, false); PrivacyManager.updateState(uid); } else if (actionId == R.id.rbTemplateCategory) PrivacyManager.applyTemplate(uid, templateName, restrictionName, false, true, false); else if (actionId == R.id.rbTemplateFull) PrivacyManager.applyTemplate(uid, templateName, restrictionName, true, true, false); else if (actionId == R.id.rbTemplateMergeSet) PrivacyManager.applyTemplate(uid, templateName, restrictionName, true, false, false); else if (actionId == R.id.rbTemplateMergeReset) PrivacyManager.applyTemplate(uid, templateName, restrictionName, true, false, true); else if (actionId == R.id.rbEnableOndemand) { PrivacyManager.setSetting(uid, PrivacyManager.cSettingOnDemand, Boolean.toString(true)); PrivacyManager.setSetting(uid, PrivacyManager.cSettingNotify, Boolean.toString(false)); } else if (actionId == R.id.rbDisableOndemand) { PrivacyManager.setSetting(uid, PrivacyManager.cSettingOnDemand, Boolean.toString(false)); PrivacyManager.setSetting(uid, PrivacyManager.cSettingNotify, Boolean.toString(true)); } else Util.log(null, Log.ERROR, "Unknown action=" + actionId); List<Boolean> newState = PrivacyManager.getRestartStates(uid, restrictionName); setState(uid, STATE_SUCCESS, !newState.equals(oldState) ? getString(R.string.msg_restart) : null); } catch (Throwable ex) { setState(uid, STATE_FAILURE, ex.getMessage()); return ex; } } finally { wl.release(); } return null; } @Override protected void onProgressUpdate(Integer... values) { blueStreakOfProgress(values[0], values[1]); super.onProgressUpdate(values); } @Override protected void onPostExecute(Throwable result) { if (!ActivityShare.this.isFinishing()) done(result); super.onPostExecute(result); } } private class ExportTask extends AsyncTask<File, Integer, Throwable> { private File mFile; @Override protected Throwable doInBackground(File... params) { // Get wakelock PowerManager pm = (PowerManager) ActivityShare.this.getSystemService(Context.POWER_SERVICE); PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "XLocation.Export"); wl.acquire(); mProgressCurrent = 0; try { mFile = params[0]; List<Integer> listUid = mAppAdapter.getListUid(); Util.log(null, Log.INFO, "Exporting " + mFile); String android_id = Secure.getString(getContentResolver(), Secure.ANDROID_ID); FileOutputStream fos = new FileOutputStream(mFile); try { // Start serialization XmlSerializer serializer = Xml.newSerializer(); serializer.setOutput(fos, "UTF-8"); serializer.startDocument(null, Boolean.valueOf(true)); serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); serializer.startTag(null, "XLocation"); // Process package map for (PackageInfo pInfo : getPackageManager().getInstalledPackages(0)) if (listUid.size() == 1 ? pInfo.applicationInfo.uid == listUid.get(0) : true) { serializer.startTag(null, "PackageInfo"); serializer.attribute(null, "Id", Integer.toString(pInfo.applicationInfo.uid)); serializer.attribute(null, "Name", pInfo.packageName); serializer.endTag(null, "PackageInfo"); } // Process global settings if (listUid.size() > 1) { List<PSetting> listGlobalSetting = PrivacyManager.getSettingList(0, null); for (PSetting setting : listGlobalSetting) { // Serialize setting serializer.startTag(null, "Setting"); serializer.attribute(null, "Id", ""); serializer.attribute(null, "Type", setting.type); serializer.attribute(null, "Name", setting.name); if (setting.value != null) serializer.attribute(null, "Value", setting.value); serializer.endTag(null, "Setting"); } } // Process application settings and restrictions for (int uid : listUid) try { if (mAbort) throw new AbortException(ActivityShare.this); publishProgress(++mProgressCurrent, listUid.size() + 1); setState(uid, STATE_RUNNING, null); // Process application settings List<PSetting> listAppSetting = PrivacyManager.getSettingList(uid, null); for (PSetting setting : listAppSetting) { // Bind accounts/contacts to same device if (Meta.cTypeAccount.equals(setting.type) || Meta.cTypeContact.equals(setting.type)) setting.name += "." + android_id; // Serialize setting serializer.startTag(null, "Setting"); serializer.attribute(null, "Id", Integer.toString(uid)); serializer.attribute(null, "Type", setting.type); serializer.attribute(null, "Name", setting.name); serializer.attribute(null, "Value", setting.value); serializer.endTag(null, "Setting"); } // Process restrictions for (String restrictionName : PrivacyManager.getRestrictions()) { // Category // TODO: use getRestrictionList PRestriction crestricted = PrivacyManager.getRestrictionEx(uid, restrictionName, null); serializer.startTag(null, "Restriction"); serializer.attribute(null, "Id", Integer.toString(uid)); serializer.attribute(null, "Name", restrictionName); serializer.attribute(null, "Restricted", Boolean.toString(crestricted.restricted)); serializer.attribute(null, "Asked", Boolean.toString(crestricted.asked)); serializer.endTag(null, "Restriction"); // Methods for (Hook md : PrivacyManager.getHooks(restrictionName, null)) { PRestriction mrestricted = PrivacyManager.getRestrictionEx(uid, restrictionName, md.getName()); if ((crestricted.restricted && !mrestricted.restricted) || (!crestricted.asked && mrestricted.asked) || md.isDangerous()) { serializer.startTag(null, "Restriction"); serializer.attribute(null, "Id", Integer.toString(uid)); serializer.attribute(null, "Name", restrictionName); serializer.attribute(null, "Method", md.getName()); serializer.attribute(null, "Restricted", Boolean.toString(mrestricted.restricted)); serializer.attribute(null, "Asked", Boolean.toString(mrestricted.asked)); serializer.endTag(null, "Restriction"); } } } setState(uid, STATE_SUCCESS, null); } catch (Throwable ex) { setState(uid, STATE_FAILURE, ex.getMessage()); throw ex; } // End serialization serializer.endTag(null, "XLocation"); serializer.endDocument(); serializer.flush(); } finally { fos.close(); } // Display message Util.log(null, Log.INFO, "Exporting finished"); return null; } catch (Throwable ex) { Util.bug(null, ex); if (mFile.exists()) mFile.delete(); return ex; } finally { wl.release(); } } @Override protected void onProgressUpdate(Integer... values) { blueStreakOfProgress(values[0], values[1]); super.onProgressUpdate(values); } @Override protected void onPostExecute(Throwable result) { if (!ActivityShare.this.isFinishing()) if (mInteractive) { done(result); // Share if (result == null) { Intent intent = new Intent(android.content.Intent.ACTION_SEND); intent.setType("text/xml"); intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + mFileName)); startActivity(Intent.createChooser(intent, String.format(getString(R.string.msg_saved_to), mFileName))); } } else { done(result); finish(); } super.onPostExecute(result); } } private class ImportTask extends AsyncTask<Object, Integer, Throwable> { @Override protected Throwable doInBackground(Object... params) { // Get wakelock PowerManager pm = (PowerManager) ActivityShare.this.getSystemService(Context.POWER_SERVICE); PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "XLocation.Import"); wl.acquire(); try { // Parameters File file = (File) params[0]; boolean clear = (Boolean) params[1]; List<Integer> listUidSelected = mAppAdapter.getListUid(); // Progress mProgressCurrent = 0; final int max = listUidSelected.size() + 1; Runnable progress = new Runnable() { @Override public void run() { publishProgress(++mProgressCurrent, max); } }; // Parse XML Util.log(null, Log.INFO, "Importing " + file); FileInputStream fis = null; Map<String, Map<String, List<ImportHandler.MethodDescription>>> mapPackage; try { fis = new FileInputStream(file); XMLReader xmlReader = SAXParserFactory.newInstance().newSAXParser().getXMLReader(); ImportHandler importHandler = new ImportHandler(clear, listUidSelected, progress); xmlReader.setContentHandler(importHandler); xmlReader.parse(new InputSource(fis)); mapPackage = importHandler.getPackageMap(); if (mAbort) throw new AbortException(ActivityShare.this); } finally { if (fis != null) fis.close(); } // Progress int progressMax = mapPackage.size(); // Process result (legacy) for (String packageName : mapPackage.keySet()) { if (mAbort) throw new AbortException(ActivityShare.this); int uid = 0; try { publishProgress(++mProgressCurrent, progressMax + 1); // Get uid uid = getPackageManager().getPackageInfo(packageName, 0).applicationInfo.uid; if (listUidSelected.contains(uid)) { Util.log(null, Log.INFO, "Importing " + packageName); setState(uid, STATE_RUNNING, null); // Reset existing restrictions List<Boolean> oldState = PrivacyManager.getRestartStates(uid, null); PrivacyManager.deleteRestrictions(uid, null, true); // Set imported restrictions for (String restrictionName : mapPackage.get(packageName).keySet()) { PrivacyManager.setRestriction(uid, restrictionName, null, true, false); for (ImportHandler.MethodDescription md : mapPackage.get(packageName) .get(restrictionName)) PrivacyManager.setRestriction(uid, restrictionName, md.getMethodName(), md.isRestricted(), false); } PrivacyManager.updateState(uid); List<Boolean> newState = PrivacyManager.getRestartStates(uid, null); setState(uid, STATE_SUCCESS, !newState.equals(oldState) ? getString(R.string.msg_restart) : null); } } catch (NameNotFoundException ignored) { } catch (Throwable ex) { if (listUidSelected.contains(uid)) setState(uid, STATE_FAILURE, ex.getMessage()); Util.bug(null, ex); } } // Display message Util.log(null, Log.INFO, "Importing finished"); return null; } catch (Throwable ex) { return ex; } finally { wl.release(); } } @Override protected void onProgressUpdate(Integer... values) { blueStreakOfProgress(values[0], values[1]); super.onProgressUpdate(values); } @Override protected void onPostExecute(Throwable result) { if (!ActivityShare.this.isFinishing()) done(result); super.onPostExecute(result); } } private class ImportHandler extends DefaultHandler { private boolean mClear; private List<Integer> mListUidSelected; private List<Integer> mListUidSettings = new ArrayList<Integer>(); private List<Integer> mListUidRestrictions = new ArrayList<Integer>(); private int lastUid = -1; private List<Boolean> mOldState = null; private SparseArray<String> mMapId = new SparseArray<String>(); private Map<String, Integer> mMapUid = new HashMap<String, Integer>(); private Map<String, Map<String, List<MethodDescription>>> mMapPackage = new HashMap<String, Map<String, List<MethodDescription>>>(); private Runnable mProgress; private String android_id = Secure.getString(getContentResolver(), Secure.ANDROID_ID); public ImportHandler(boolean clear, List<Integer> listUidSelected, Runnable progress) { mClear = clear; mListUidSelected = listUidSelected; mProgress = progress; } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) { try { if (qName.equals("XLocation")) { // Root } else if (qName.equals("PackageInfo")) { // Package info int id = Integer.parseInt(attributes.getValue("Id")); String name = attributes.getValue("Name"); mMapId.put(id, name); } else if (qName.equals("Setting")) { // Setting String id = attributes.getValue("Id"); String type = attributes.getValue("Type"); String name = attributes.getValue("Name"); String value = attributes.getValue("Value"); // Failsafe if (name == null) return; // Do not import version number if (name.equals(PrivacyManager.cSettingVersion)) return; // Decode legacy type if (name.startsWith("Account.") || name.startsWith("Application.") || name.startsWith("Contact.") || name.startsWith("Template.") || name.startsWith("Whitelist.")) { name = name.replace("Whitelist.", ""); int dot = name.indexOf('.'); type = name.substring(0, dot); name = name.substring(dot + 1); } else if (type == null) type = ""; // Import accounts/contacts only for same device if (Meta.cTypeAccount.equals(type) || Meta.cTypeContact.equals(type)) if (name.endsWith("." + android_id)) name = name.replace("." + android_id, ""); else return; if (id == null) { // Legacy Util.log(null, Log.WARN, "Legacy " + name + "=" + value); int userId = Util.getUserId(Process.myUid()); PrivacyManager.setSetting(userId, name, value); } else if ("".equals(id)) { // Global setting if (mListUidSelected.size() > 1) { int userId = Util.getUserId(Process.myUid()); PrivacyManager.setSetting(userId, type, name, value); } } else { // Application setting int iid = Integer.parseInt(id); int uid = getUid(iid); if (mListUidSelected.contains(uid)) { // Check for abort if (mAbort && !mListUidSettings.contains(uid)) { setState(uid, STATE_FAILURE); setMessage(uid, getString(R.string.msg_aborted)); return; } // Check for new uid if (!mListUidSettings.contains(uid)) { // Mark previous as success if (lastUid > 0) { boolean restart = !PrivacyManager.getRestartStates(lastUid, null) .equals(mOldState); setState(lastUid, STATE_SUCCESS, restart ? getString(R.string.msg_restart) : null); } // Update state lastUid = uid; mListUidSettings.add(uid); // Update visible state setState(uid, STATE_RUNNING, null); // Clear settings if (mClear) PrivacyManager.deleteSettings(uid); } PrivacyManager.setSetting(uid, type, name, value); } } } else if (qName.equals("Package")) { // Restriction (legacy) String packageName = attributes.getValue("Name"); String restrictionName = attributes.getValue("Restriction"); String methodName = attributes.getValue("Method"); boolean restricted = Boolean.parseBoolean(attributes.getValue("Restricted")); Util.log(null, Log.WARN, "Legacy package=" + packageName + " " + restrictionName + "/" + methodName + "=" + restricted); // Map package restriction if (!mMapPackage.containsKey(packageName)) mMapPackage.put(packageName, new HashMap<String, List<MethodDescription>>()); if (!mMapPackage.get(packageName).containsKey(restrictionName)) mMapPackage.get(packageName).put(restrictionName, new ArrayList<MethodDescription>()); if (methodName != null) { MethodDescription md = new MethodDescription(methodName, restricted); mMapPackage.get(packageName).get(restrictionName).add(md); } } else if (qName.equals("Restriction")) { // Restriction (new style) int id = Integer.parseInt(attributes.getValue("Id")); String restrictionName = attributes.getValue("Name"); String methodName = attributes.getValue("Method"); boolean restricted = Boolean.parseBoolean(attributes.getValue("Restricted")); boolean asked = Boolean.parseBoolean(attributes.getValue("Asked")); // Get uid int uid = getUid(id); if (mListUidSelected.contains(uid)) { // Check for abort if (mAbort && !mListUidRestrictions.contains(uid)) { setState(uid, STATE_FAILURE); setMessage(uid, getString(R.string.msg_aborted)); return; } // Check for new uid if (!mListUidRestrictions.contains(uid)) { // Mark previous as success if (lastUid > 0) { PrivacyManager.updateState(lastUid); boolean restart = !PrivacyManager.getRestartStates(lastUid, null).equals(mOldState); setState(lastUid, STATE_SUCCESS, restart ? getString(R.string.msg_restart) : null); } // Update state lastUid = uid; mListUidRestrictions.add(uid); mOldState = PrivacyManager.getRestartStates(uid, null); // Update visible state setState(uid, STATE_RUNNING, null); runOnUiThread(mProgress); // Clear restrictions if (mClear) PrivacyManager.deleteRestrictions(uid, null, false); } // Set restriction PrivacyManager.setRestriction(uid, restrictionName, methodName, restricted, asked); } } else Util.log(null, Log.WARN, "Unknown element name=" + qName); } catch (Throwable ex) { Util.bug(null, ex); } } @Override public void endElement(String uri, String localName, String qName) { if (qName.equals("XLocation")) { if (lastUid > 0) { PrivacyManager.updateState(lastUid); boolean restart = !PrivacyManager.getRestartStates(lastUid, null).equals(mOldState); setState(lastUid, STATE_SUCCESS, restart ? getString(R.string.msg_restart) : null); } // Cleanup salt int userId = Util.getUserId(Process.myUid()); PrivacyManager.removeLegacySalt(userId); } } private int getUid(int id) { String packageName = mMapId.get(id); if (packageName == null) { Util.log(null, Log.WARN, "Unknown id=" + id); return -1; } else if (!mMapUid.containsKey(packageName)) try { int newuid = ActivityShare.this.getPackageManager().getPackageInfo(packageName, 0).applicationInfo.uid; mMapUid.put(packageName, newuid); } catch (NameNotFoundException ex) { // Do not lookup again mMapUid.put(packageName, -1); Util.log(null, Log.WARN, "Unknown package name=" + packageName); } return (mMapUid.containsKey(packageName) ? mMapUid.get(packageName) : -1); } public Map<String, Map<String, List<MethodDescription>>> getPackageMap() { return mMapPackage; } public class MethodDescription { private String mMethodName; private boolean mRestricted; public MethodDescription(String methodName, boolean restricted) { mMethodName = methodName; mRestricted = restricted; } public String getMethodName() { return mMethodName; } public boolean isRestricted() { return mRestricted; } } } private class FetchTask extends AsyncTask<Boolean, Integer, Throwable> { @Override @SuppressLint("DefaultLocale") protected Throwable doInBackground(Boolean... params) { // Get wakelock PowerManager pm = (PowerManager) ActivityShare.this.getSystemService(Context.POWER_SERVICE); PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "XLocation.Fetch"); wl.acquire(); try { // Get data boolean clear = params[0]; List<ApplicationInfoEx> lstApp = mAppAdapter.getListAppInfo(); int userId = Util.getUserId(Process.myUid()); String[] license = Util.getProLicenseUnchecked(); String android_id = Secure.getString(ActivityShare.this.getContentResolver(), Secure.ANDROID_ID); PackageInfo xInfo = getPackageManager().getPackageInfo(getPackageName(), 0); String confidence = PrivacyManager.getSetting(userId, PrivacyManager.cSettingConfidence, ""); // Initialize progress mProgressCurrent = 0; // Process applications for (ApplicationInfoEx appInfo : lstApp) try { publishProgress(++mProgressCurrent, lstApp.size() + 1); if (mAbort) throw new AbortException(ActivityShare.this); setState(appInfo.getUid(), STATE_RUNNING, ActivityShare.this.getString(R.string.menu_fetch)); JSONArray appName = new JSONArray(); for (String name : appInfo.getApplicationName()) appName.put(name); JSONArray pkgName = new JSONArray(); for (String name : appInfo.getPackageName()) pkgName.put(name); JSONArray pkgVersion = new JSONArray(); for (String version : appInfo.getPackageVersionName(ActivityShare.this)) pkgVersion.put(version); // Encode package JSONObject jRoot = new JSONObject(); jRoot.put("protocol_version", cProtocolVersion); jRoot.put("android_id", Util.md5(android_id).toLowerCase()); jRoot.put("android_sdk", Build.VERSION.SDK_INT); jRoot.put("xlocation_version", xInfo.versionCode); jRoot.put("application_name", appName); jRoot.put("package_name", pkgName); jRoot.put("package_version", pkgVersion); jRoot.put("email", license[1]); jRoot.put("signature", license[2]); jRoot.put("confidence", confidence); // Fetch HttpParams httpParams = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(httpParams, TIMEOUT_MILLISEC); HttpConnectionParams.setSoTimeout(httpParams, TIMEOUT_MILLISEC); HttpClient httpclient = new DefaultHttpClient(httpParams); HttpPost httpost = new HttpPost(getBaseURL() + "?format=json&action=fetch"); httpost.setEntity(new ByteArrayEntity(jRoot.toString().getBytes("UTF-8"))); httpost.setHeader("Accept", "application/json"); httpost.setHeader("Content-type", "application/json"); HttpResponse response = httpclient.execute(httpost); StatusLine statusLine = response.getStatusLine(); if (mAbort) throw new AbortException(ActivityShare.this); setState(appInfo.getUid(), STATE_RUNNING, ActivityShare.this.getString(R.string.msg_applying)); if (statusLine.getStatusCode() == HttpStatus.SC_OK) { // Succeeded ByteArrayOutputStream out = new ByteArrayOutputStream(); response.getEntity().writeTo(out); out.close(); // Deserialize JSONObject status = new JSONObject(out.toString("UTF-8")); if (status.getBoolean("ok")) { JSONArray settings = status.getJSONArray("settings"); // Delete existing restrictions List<Boolean> oldState = PrivacyManager.getRestartStates(appInfo.getUid(), null); // Clear existing restriction if (clear) PrivacyManager.deleteRestrictions(appInfo.getUid(), null, true); // Set fetched restrictions List<PRestriction> listRestriction = new ArrayList<PRestriction>(); for (int i = 0; i < settings.length(); i++) { JSONObject entry = settings.getJSONObject(i); String restrictionName = entry.getString("restriction"); String methodName = entry.has("method") ? entry.getString("method") : null; int voted_restricted = entry.getInt("restricted"); int voted_not_restricted = entry.getInt("not_restricted"); boolean restricted = (voted_restricted > voted_not_restricted); if (clear || restricted) listRestriction.add(new PRestriction(appInfo.getUid(), restrictionName, methodName, restricted)); } PrivacyManager.setRestrictionList(listRestriction); List<Boolean> newState = PrivacyManager.getRestartStates(appInfo.getUid(), null); // Mark as new/changed PrivacyManager.setSetting(appInfo.getUid(), PrivacyManager.cSettingState, Integer.toString(ApplicationInfoEx.STATE_ATTENTION)); // Change app modification time PrivacyManager.setSetting(appInfo.getUid(), PrivacyManager.cSettingModifyTime, Long.toString(System.currentTimeMillis())); setState(appInfo.getUid(), STATE_SUCCESS, !newState.equals(oldState) ? getString(R.string.msg_restart) : null); } else { int errno = status.getInt("errno"); String message = status.getString("error"); ServerException ex = new ServerException(ActivityShare.this, errno, message); setState(appInfo.getUid(), STATE_FAILURE, ex.getMessage()); } } else { // Failed response.getEntity().getContent().close(); throw new IOException(statusLine.getReasonPhrase()); } } catch (Throwable ex) { setState(appInfo.getUid(), STATE_FAILURE, ex.getMessage()); throw ex; } return null; } catch (ConnectTimeoutException ex) { return ex; } catch (HttpHostConnectException ex) { return ex; } catch (SocketTimeoutException ex) { return ex; } catch (SSLException ex) { return ex; } catch (UnknownHostException ex) { return ex; } catch (IOException ex) { return ex; } catch (Throwable ex) { Util.bug(null, ex); return ex; } finally { wl.release(); } } @Override protected void onProgressUpdate(Integer... values) { blueStreakOfProgress(values[0], values[1]); super.onProgressUpdate(values); } @Override protected void onPostExecute(Throwable result) { if (!ActivityShare.this.isFinishing()) done(result); super.onPostExecute(result); } } @SuppressLint("DefaultLocale") private class SubmitTask extends AsyncTask<Object, Integer, Throwable> { @Override protected Throwable doInBackground(Object... params) { // Get wakelock PowerManager pm = (PowerManager) ActivityShare.this.getSystemService(Context.POWER_SERVICE); PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "XLocation.Submit"); wl.acquire(); try { // Get data List<ApplicationInfoEx> lstApp = mAppAdapter.getListAppInfo(); // Initialize progress mProgressCurrent = 0; for (ApplicationInfoEx appInfo : lstApp) try { if (mAbort) throw new AbortException(ActivityShare.this); // Update progess publishProgress(++mProgressCurrent, lstApp.size() + 1); setState(appInfo.getUid(), STATE_RUNNING, ActivityShare.this.getString(R.string.msg_loading)); // Check if any account allowed boolean allowedAccounts = false; AccountManager accountManager = AccountManager.get(ActivityShare.this); for (Account account : accountManager.getAccounts()) { String sha1 = Util.sha1(account.name + account.type); boolean allowed = PrivacyManager.getSettingBool(appInfo.getUid(), Meta.cTypeAccount, sha1, false); if (allowed) { allowedAccounts = true; break; } } // Check if any application allowed boolean allowedApplications = false; for (ApplicationInfoEx aAppInfo : ApplicationInfoEx.getXApplicationList(ActivityShare.this, null)) for (String packageName : aAppInfo.getPackageName()) { boolean allowed = PrivacyManager.getSettingBool(-appInfo.getUid(), Meta.cTypeApplication, packageName, false); if (allowed) { allowedApplications = true; break; } } // Check if any contact allowed boolean allowedContacts = false; Cursor cursor = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, new String[] { ContactsContract.Contacts._ID }, null, null, null); if (cursor != null) try { while (cursor.moveToNext()) { long id = cursor.getLong(cursor.getColumnIndex(ContactsContract.Contacts._ID)); boolean allowed = PrivacyManager.getSettingBool(-appInfo.getUid(), Meta.cTypeContact, Long.toString(id), false); if (allowed) { allowedContacts = true; break; } } } finally { cursor.close(); } // Get white lists Map<String, TreeMap<String, Boolean>> mapWhitelist = PrivacyManager .listWhitelisted(appInfo.getUid(), null); // Encode restrictions JSONArray jSettings = new JSONArray(); for (String restrictionName : PrivacyManager.getRestrictions()) { // TODO: use getRestrictionList boolean restricted = PrivacyManager.getRestrictionEx(appInfo.getUid(), restrictionName, null).restricted; // Category long used = PrivacyManager.getUsage(appInfo.getUid(), restrictionName, null); JSONObject jRestriction = new JSONObject(); jRestriction.put("restriction", restrictionName); jRestriction.put("restricted", restricted); jRestriction.put("used", used); if (restrictionName.equals(PrivacyManager.cAccounts)) jRestriction.put("allowed", allowedAccounts ? 1 : 0); else if (restrictionName.equals(PrivacyManager.cSystem)) jRestriction.put("allowed", allowedApplications ? 1 : 0); else if (restrictionName.equals(PrivacyManager.cContacts)) jRestriction.put("allowed", allowedContacts ? 1 : 0); jSettings.put(jRestriction); // Methods for (Hook md : PrivacyManager.getHooks(restrictionName, null)) { boolean mRestricted = restricted && PrivacyManager.getRestrictionEx(appInfo.getUid(), restrictionName, md.getName()).restricted; long mUsed = PrivacyManager.getUsage(appInfo.getUid(), restrictionName, md.getName()); boolean mWhitelisted = false; if (md.whitelist() != null && mapWhitelist.containsKey(md.whitelist())) for (Boolean allowed : mapWhitelist.get(md.whitelist()).values()) if (mRestricted ? allowed : !allowed) { mWhitelisted = true; break; } JSONObject jMethod = new JSONObject(); jMethod.put("restriction", restrictionName); jMethod.put("method", md.getName()); jMethod.put("restricted", mRestricted); jMethod.put("used", mUsed); jMethod.put("allowed", mWhitelisted ? 1 : 0); jSettings.put(jMethod); } } // Get data String[] license = Util.getProLicenseUnchecked(); PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0); String android_id = Secure.getString(ActivityShare.this.getContentResolver(), Secure.ANDROID_ID); JSONArray appName = new JSONArray(); for (String name : appInfo.getApplicationName()) appName.put(name); JSONArray pkgName = new JSONArray(); for (String name : appInfo.getPackageName()) pkgName.put(name); JSONArray pkgVersionName = new JSONArray(); for (String version : appInfo.getPackageVersionName(ActivityShare.this)) pkgVersionName.put(version); JSONArray pkgVersionCode = new JSONArray(); for (Integer version : appInfo.getPackageVersionCode(ActivityShare.this)) pkgVersionCode.put((int) version); // Encode package JSONObject jRoot = new JSONObject(); jRoot.put("protocol_version", cProtocolVersion); jRoot.put("android_id", Util.md5(android_id).toLowerCase()); jRoot.put("android_sdk", Build.VERSION.SDK_INT); jRoot.put("xlocation_version", pInfo.versionCode); jRoot.put("application_name", appName); jRoot.put("package_name", pkgName); jRoot.put("package_version_name", pkgVersionName); jRoot.put("package_version_code", pkgVersionCode); jRoot.put("settings", jSettings); if (license != null) { jRoot.put("email", license[1]); jRoot.put("signature", license[2]); } if (mAbort) throw new AbortException(ActivityShare.this); setState(appInfo.getUid(), STATE_RUNNING, ActivityShare.this.getString(R.string.menu_submit)); // Submit HttpParams httpParams = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(httpParams, TIMEOUT_MILLISEC); HttpConnectionParams.setSoTimeout(httpParams, TIMEOUT_MILLISEC); HttpClient httpclient = new DefaultHttpClient(httpParams); HttpPost httpost = new HttpPost(getBaseURL() + "?format=json&action=submit"); httpost.setEntity(new ByteArrayEntity(jRoot.toString().getBytes("UTF-8"))); httpost.setHeader("Accept", "application/json"); httpost.setHeader("Content-type", "application/json"); HttpResponse response = httpclient.execute(httpost); StatusLine statusLine = response.getStatusLine(); if (statusLine.getStatusCode() == HttpStatus.SC_OK) { // Succeeded ByteArrayOutputStream out = new ByteArrayOutputStream(); response.getEntity().writeTo(out); out.close(); JSONObject status = new JSONObject(out.toString("UTF-8")); if (status.getBoolean("ok")) { // Mark as shared PrivacyManager.setSetting(appInfo.getUid(), PrivacyManager.cSettingState, Integer.toString(ApplicationInfoEx.STATE_SHARED)); setState(appInfo.getUid(), STATE_SUCCESS, null); } else { int errno = status.getInt("errno"); String message = status.getString("error"); ServerException ex = new ServerException(ActivityShare.this, errno, message); // Mark as unregistered if (errno == ServerException.cErrorNotActivated) { int userId = Util.getUserId(Process.myUid()); PrivacyManager.setSetting(userId, PrivacyManager.cSettingRegistered, Boolean.toString(false)); throw ex; } else setState(appInfo.getUid(), STATE_FAILURE, ex.getMessage()); } } else { // Failed response.getEntity().getContent().close(); throw new IOException(statusLine.getReasonPhrase()); } } catch (Throwable ex) { setState(appInfo.getUid(), STATE_FAILURE, ex.getMessage()); throw ex; } return null; } catch (ConnectTimeoutException ex) { return ex; } catch (HttpHostConnectException ex) { return ex; } catch (SocketTimeoutException ex) { return ex; } catch (SSLException ex) { return ex; } catch (UnknownHostException ex) { return ex; } catch (IOException ex) { return ex; } catch (Throwable ex) { Util.bug(null, ex); return ex; } finally { wl.release(); } } @Override protected void onProgressUpdate(Integer... values) { blueStreakOfProgress(values[0], values[1]); super.onProgressUpdate(values); } @Override protected void onPostExecute(Throwable result) { if (!ActivityShare.this.isFinishing()) done(result); super.onPostExecute(result); } } @SuppressLint("InflateParams") public static boolean registerDevice(final ActivityBase context) { int userId = Util.getUserId(Process.myUid()); if (Util.hasProLicense(context) == null && !PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingRegistered, false)) { // Get accounts String email = null; for (Account account : AccountManager.get(context).getAccounts()) if ("com.google".equals(account.type)) { email = account.name; break; } LayoutInflater LayoutInflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); View view = LayoutInflater.inflate(R.layout.register, null); final EditText input = (EditText) view.findViewById(R.id.etEmail); if (email != null) input.setText(email); // Build dialog AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); alertDialogBuilder.setTitle(R.string.msg_register); alertDialogBuilder.setIcon(context.getThemed(R.attr.icon_launcher)); alertDialogBuilder.setView(view); alertDialogBuilder.setPositiveButton(context.getString(android.R.string.ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String email = input.getText().toString(); if (Patterns.EMAIL_ADDRESS.matcher(email).matches()) new RegisterTask(context).executeOnExecutor(mExecutor, email); } }); alertDialogBuilder.setNegativeButton(context.getString(android.R.string.cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // Do nothing } }); // Show dialog AlertDialog alertDialog = alertDialogBuilder.create(); alertDialog.show(); return false; } return true; } @SuppressLint("DefaultLocale") private static class RegisterTask extends AsyncTask<String, String, Throwable> { private ActivityBase mContext; public RegisterTask(ActivityBase context) { mContext = context; } protected Throwable doInBackground(String... params) { // Get wakelock PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "XLocation.Register"); wl.acquire(); try { String android_id = Secure.getString(mContext.getContentResolver(), Secure.ANDROID_ID); // Encode message JSONObject jRoot = new JSONObject(); jRoot.put("protocol_version", cProtocolVersion); jRoot.put("email", params[0]); jRoot.put("android_id", Util.md5(android_id).toLowerCase()); // Submit HttpParams httpParams = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(httpParams, TIMEOUT_MILLISEC); HttpConnectionParams.setSoTimeout(httpParams, TIMEOUT_MILLISEC); HttpClient httpclient = new DefaultHttpClient(httpParams); HttpPost httpost = new HttpPost(getBaseURL() + "device?format=json&action=register"); httpost.setEntity(new ByteArrayEntity(jRoot.toString().getBytes("UTF-8"))); httpost.setHeader("Accept", "application/json"); httpost.setHeader("Content-type", "application/json"); HttpResponse response = httpclient.execute(httpost); StatusLine statusLine = response.getStatusLine(); if (statusLine.getStatusCode() == HttpStatus.SC_OK) { // Succeeded ByteArrayOutputStream out = new ByteArrayOutputStream(); response.getEntity().writeTo(out); out.close(); JSONObject status = new JSONObject(out.toString("UTF-8")); if (status.getBoolean("ok")) { // Mark as registered int userId = Util.getUserId(Process.myUid()); PrivacyManager.setSetting(userId, PrivacyManager.cSettingRegistered, Boolean.toString(true)); return null; } else { int errno = status.getInt("errno"); String message = status.getString("error"); throw new ServerException(errno, message); } } else { // Failed response.getEntity().getContent().close(); throw new IOException(statusLine.getReasonPhrase()); } } catch (ConnectTimeoutException ex) { return ex; } catch (HttpHostConnectException ex) { return ex; } catch (SocketTimeoutException ex) { return ex; } catch (SSLException ex) { return ex; } catch (UnknownHostException ex) { return ex; } catch (IOException ex) { return ex; } catch (Throwable ex) { Util.bug(null, ex); return ex; } finally { wl.release(); } } @Override protected void onPostExecute(Throwable result) { if (!mContext.isFinishing()) { String message; if (result == null) message = mContext.getString(R.string.msg_registered); else message = result.getMessage(); // Build dialog AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(mContext); alertDialogBuilder.setTitle(R.string.app_name); alertDialogBuilder.setMessage(message); alertDialogBuilder.setIcon(mContext.getThemed(R.attr.icon_launcher)); alertDialogBuilder.setPositiveButton(mContext.getString(android.R.string.ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }); // Show dialog AlertDialog alertDialog = alertDialogBuilder.create(); alertDialog.show(); } super.onPostExecute(result); } } public static class UpdateTask extends AsyncTask<Object, Object, Object> { private Context mContext; private NotificationCompat.Builder builder; private Notification notification; private NotificationManager notificationManager; public UpdateTask(Context context) { mContext = context; builder = new NotificationCompat.Builder(context); notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); } @Override protected void onPreExecute() { // Build notification builder.setSmallIcon(R.drawable.ic_launcher); builder.setContentTitle(mContext.getString(R.string.app_name)); builder.setAutoCancel(false); builder.setOngoing(true); } @Override @SuppressLint("DefaultLocale") protected Object doInBackground(Object... args) { try { // Notify builder.setContentText(mContext.getString(R.string.title_update_checking)); builder.setWhen(System.currentTimeMillis()); notification = builder.build(); notificationManager.notify(Util.NOTIFY_UPDATE, notification); // Encode package String[] license = Util.getProLicenseUnchecked(); int userId = Util.getUserId(Process.myUid()); boolean test = PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingTestVersions, false); String android_id = Secure.getString(mContext.getContentResolver(), Secure.ANDROID_ID); JSONObject jRoot = new JSONObject(); jRoot.put("protocol_version", cProtocolVersion); jRoot.put("android_id", Util.md5(android_id).toLowerCase()); jRoot.put("android_sdk", Build.VERSION.SDK_INT); jRoot.put("xlocation_version", Util.getSelfVersionCode(mContext)); jRoot.put("xlocation_version_name", Util.getSelfVersionName(mContext)); jRoot.put("test_versions", test); jRoot.put("email", license[1]); jRoot.put("signature", license[2]); // Update HttpParams httpParams = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(httpParams, TIMEOUT_MILLISEC); HttpConnectionParams.setSoTimeout(httpParams, TIMEOUT_MILLISEC); HttpClient httpclient = new DefaultHttpClient(httpParams); HttpPost httpost = new HttpPost(getBaseURL() + "?format=json&action=update"); httpost.setEntity(new ByteArrayEntity(jRoot.toString().getBytes("UTF-8"))); httpost.setHeader("Accept", "application/json"); httpost.setHeader("Content-type", "application/json"); HttpResponse response = httpclient.execute(httpost); StatusLine statusLine = response.getStatusLine(); if (statusLine.getStatusCode() == HttpStatus.SC_OK) { String contentType = response.getFirstHeader("Content-Type").getValue(); if ("application/octet-stream".equals(contentType)) { // Update notification builder.setContentText(mContext.getString(R.string.title_update_downloading)); builder.setWhen(System.currentTimeMillis()); notification = builder.build(); notificationManager.notify(Util.NOTIFY_UPDATE, notification); // Download APK File folder = Environment .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); folder.mkdirs(); String fileName = response.getFirstHeader("Content-Disposition").getElements()[0] .getParameterByName("filename").getValue(); File download = new File(folder, fileName); FileOutputStream fos = null; try { fos = new FileOutputStream(download); response.getEntity().writeTo(fos); } finally { if (fos != null) fos.close(); } return download; } else if ("application/json".equals(contentType)) { ByteArrayOutputStream out = new ByteArrayOutputStream(); response.getEntity().writeTo(out); out.close(); throw new IOException(out.toString("UTF-8")); } else throw new IOException(contentType); } else return statusLine; } catch (Throwable ex) { Util.bug(null, ex); return ex; } } @Override protected void onPostExecute(Object result) { if (result instanceof StatusLine) { notificationManager.cancel(Util.NOTIFY_UPDATE); StatusLine status = (StatusLine) result; if (status.getStatusCode() == 204) { // No Content String none = mContext.getString(R.string.title_update_none); Toast.makeText(mContext, none, Toast.LENGTH_LONG).show(); } else Toast.makeText(mContext, status.getStatusCode() + " " + status.getReasonPhrase(), Toast.LENGTH_LONG).show(); } else if (result instanceof Throwable) { notificationManager.cancel(Util.NOTIFY_UPDATE); Throwable ex = (Throwable) result; Toast.makeText(mContext, ex.toString(), Toast.LENGTH_LONG).show(); } else { File download = (File) result; Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(download), "application/vnd.android.package-archive"); PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); // Update notification builder.setContentText(mContext.getString(R.string.title_update_install)); builder.setWhen(System.currentTimeMillis()); builder.setAutoCancel(true); builder.setOngoing(false); builder.setContentIntent(pi); notification = builder.build(); notificationManager.notify(Util.NOTIFY_UPDATE, notification); } } } // Helper methods private void blueStreakOfProgress(Integer current, Integer max) { // Set up the progress bar if (mProgressWidth == 0) { final View vShareProgressEmpty = (View) findViewById(R.id.vShareProgressEmpty); mProgressWidth = vShareProgressEmpty.getMeasuredWidth(); } // Display stuff if (max == 0) max = 1; int width = (int) ((float) mProgressWidth) * current / max; View vShareProgressFull = (View) findViewById(R.id.vShareProgressFull); vShareProgressFull.getLayoutParams().width = width; vShareProgressFull.invalidate(); vShareProgressFull.requestLayout(); } private void done(Throwable ex) { String result = null; if (ex != null && !(ex instanceof AbortException)) result = ex.getMessage(); // Check result string and display toast with error if (result != null) Toast.makeText(this, result, Toast.LENGTH_LONG).show(); // Reset progress bar blueStreakOfProgress(0, 1); mRunning = false; // Update buttons final Button btnCancel = (Button) findViewById(R.id.btnCancel); final Button btnOk = (Button) findViewById(R.id.btnOk); btnCancel.setEnabled(false); btnOk.setEnabled(true); // Handle close btnOk.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { finish(); } }); } public void fileChooser() { Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT); Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath() + "/.xlocation/"); chooseFile.setDataAndType(uri, "text/xml"); Intent intent = Intent.createChooser(chooseFile, getString(R.string.app_name)); startActivityForResult(intent, ACTIVITY_IMPORT_SELECT); } private void showFileName() { TextView tvDescription = (TextView) findViewById(R.id.tvDescription); tvDescription.setText(mFileName); Button btnOk = (Button) findViewById(R.id.btnOk); btnOk.setEnabled(true); } public static String getBaseURL() { int userId = Util.getUserId(Process.myUid()); if (PrivacyManager.getSettingBool(userId, PrivacyManager.cSettingHttps, true)) return HTTPS_BASE_URL; else return HTTP_BASE_URL; } public static String getFileName(Context context, boolean multiple, String packageName) { File folder = new File( Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + ".xlocation"); folder.mkdir(); String fileName; if (multiple) { SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.ROOT); fileName = String.format("%s_XLocation_%s_%s%s.xml", format.format(new Date()), Util.getSelfVersionName(context), Build.DEVICE, (packageName == null ? "" : "_" + packageName)); } else fileName = "XLocation.xml"; return new File(folder + File.separator + fileName).getAbsolutePath(); } // Helper classes public static class AbortException extends Exception { private static final long serialVersionUID = 1L; public AbortException(Context context) { super(context.getString(R.string.msg_aborted)); } } public static class ServerException extends Exception { private int mErrorNo; private Context mContext = null; private static final long serialVersionUID = 1L; public final static int cErrorNotActivated = 206; public final static int cErrorNoRestrictions = 305; public ServerException(int errorno, String message) { super(message); mErrorNo = errorno; } public ServerException(Context context, int errorno, String message) { super(message); mErrorNo = errorno; mContext = context; } @Override @SuppressLint("DefaultLocale") public String getMessage() { if (mErrorNo == cErrorNoRestrictions && mContext != null) return mContext.getString(R.string.msg_no_restrictions); return String.format("Error %d: %s", mErrorNo, super.getMessage()); // general: // 'errno' => 101, 'error' => 'Empty request' // 'errno' => 102, 'error' => 'Please upgrade to at least ...' // 'errno' => 103, 'error' => 'Error connecting to database' // 'errno' => 104, 'error' => 'Unknown action: ...' // submit: // 'errno' => 201, 'error' => 'Not authorized' // 'errno' => 202, 'error' => 'Android ID missing' // 'errno' => 203, 'error' => 'Package name missing' // 'errno' => 204, 'error' => 'Too many packages for application' // 'errno' => 205, 'error' => 'Error storing restrictions' // 'errno' => 206, 'error' => 'Device not activated' // fetch: // 'errno' => 301, 'error' => 'Not authorized' // 'errno' => 303, 'error' => 'Package name missing' // 'errno' => 304, 'error' => 'Too many packages for application' // 'errno' => 305, 'error' => 'No restrictions available' // 'errno' => 306, 'error' => 'Error retrieving restrictions' // 'errno' => 307, 'error' => 'There is a maximum of ...' } } }