package org.fdroid.fdroid.views; import android.annotation.TargetApi; import android.app.Activity; import android.app.Dialog; import android.app.ProgressDialog; import android.content.*; import android.content.res.Configuration; import android.net.Uri; import android.net.wifi.WifiManager; import android.nfc.NdefMessage; import android.nfc.NdefRecord; import android.nfc.NfcAdapter; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; import android.util.Log; import android.view.*; import android.widget.*; import org.fdroid.fdroid.*; import org.fdroid.fdroid.localrepo.LocalRepoService; import org.fdroid.fdroid.net.WifiStateChangeService; import java.util.Locale; import java.util.Timer; import java.util.TimerTask; public class LocalRepoActivity extends Activity { private static final String TAG = "LocalRepoActivity"; private ProgressDialog repoProgress; private WifiManager wifiManager; private Button enableWifiButton; private CheckBox repoSwitch; private Timer stopTimer; private int SET_IP_ADDRESS = 7345; private int UPDATE_REPO = 7346; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ((FDroidApp) getApplication()).applyTheme(this); setContentView(R.layout.local_repo_activity); enableWifiButton = (Button) findViewById(R.id.enable_wifi); repoSwitch = (CheckBox) findViewById(R.id.repoSwitch); wifiManager = (WifiManager) getSystemService(WIFI_SERVICE); } @Override public void onResume() { super.onResume(); resetNetworkInfo(); LocalBroadcastManager.getInstance(this).registerReceiver(onWifiChange, new IntentFilter(WifiStateChangeService.BROADCAST)); LocalBroadcastManager.getInstance(this).registerReceiver(onLocalRepoChange, new IntentFilter(LocalRepoService.STATE)); // if no local repo exists, create one with only FDroid in it if (!FDroidApp.localRepo.xmlIndex.exists()) new UpdateAsyncTask(this, new String[] { getPackageName(), }).execute(); // start repo by default FDroidApp.startLocalRepoService(LocalRepoActivity.this); // automatically turn off after 15 minutes stopTimer = new Timer(); stopTimer.schedule(new TimerTask() { @Override public void run() { FDroidApp.stopLocalRepoService(LocalRepoActivity.this); } }, 900000); // 15 minutes } @Override public void onPause() { super.onPause(); LocalBroadcastManager.getInstance(this).unregisterReceiver(onWifiChange); LocalBroadcastManager.getInstance(this).unregisterReceiver(onLocalRepoChange); } private BroadcastReceiver onWifiChange = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent i) { resetNetworkInfo(); } }; private BroadcastReceiver onLocalRepoChange = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent i) { String state = i.getStringExtra(LocalRepoService.STATE); if (state != null && state.equals(LocalRepoService.STARTED)) setRepoSwitchChecked(true); else setRepoSwitchChecked(false); } }; private void resetNetworkInfo() { int wifiState = wifiManager.getWifiState(); if (wifiState == WifiManager.WIFI_STATE_ENABLED) { setUIFromWifi(); wireRepoSwitchToWebServer(); repoSwitch.setVisibility(View.VISIBLE); enableWifiButton.setVisibility(View.GONE); } else { repoSwitch.setChecked(false); repoSwitch.setVisibility(View.GONE); enableWifiButton.setVisibility(View.VISIBLE); enableWifiButton.setText(R.string.enable_wifi); enableWifiButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { enableWifiButton.setText(R.string.enabling_wifi); wifiManager.setWifiEnabled(true); /* * Once the wifi is connected to a network, then * WifiStateChangeReceiver will receive notice, and kick off * the process of getting the info about the wifi * connection. */ } }); } } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.local_repo_activity, menu); if (Build.VERSION.SDK_INT < 11) // TODO remove after including appcompat-v7 menu.findItem(R.id.menu_setup_repo).setVisible(false); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_setup_repo: startActivityForResult(new Intent(this, SelectLocalAppsActivity.class), UPDATE_REPO); return true; case R.id.menu_send_fdroid_via_wifi: startActivity(new Intent(this, QrWizardWifiNetworkActivity.class)); return true; case R.id.menu_settings: startActivityForResult(new Intent(this, PreferencesActivity.class), SET_IP_ADDRESS); return true; } return false; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode != Activity.RESULT_OK) return; if (requestCode == SET_IP_ADDRESS) { setUIFromWifi(); } else if (requestCode == UPDATE_REPO) { setUIFromWifi(); new UpdateAsyncTask(this, FDroidApp.selectedApps.toArray(new String[0])) .execute(); } } @Override protected Dialog onCreateDialog(int id) { switch (id) { case 0: repoProgress = new ProgressDialog(this); repoProgress.setMessage("Scanning Apps. Please wait..."); repoProgress.setIndeterminate(false); repoProgress.setMax(100); repoProgress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); repoProgress.setCancelable(false); repoProgress.show(); return repoProgress; default: return null; } } private void wireRepoSwitchToWebServer() { repoSwitch.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setRepoSwitchChecked(repoSwitch.isChecked()); if (repoSwitch.isChecked()) { FDroidApp.startLocalRepoService(LocalRepoActivity.this); } else { FDroidApp.stopLocalRepoService(LocalRepoActivity.this); stopTimer.cancel(); // disable automatic stop } } }); } private void setRepoSwitchChecked(boolean checked) { repoSwitch.setChecked(checked); if (checked) { repoSwitch.setText(R.string.local_repo_running); } else { repoSwitch.setText(R.string.touch_to_turn_on_local_repo); } } @TargetApi(14) private void setUIFromWifi() { if (TextUtils.isEmpty(FDroidApp.repo.address)) return; // the fingerprint is not useful on the button label String buttonLabel = FDroidApp.repo.address.replaceAll("\\?.*$", ""); TextView sharingUriTextView = (TextView) findViewById(R.id.sharing_uri); sharingUriTextView.setText(buttonLabel); /* * Set URL to UPPER for compact QR Code, FDroid will translate it back. * Remove the SSID from the query string since SSIDs are case-sensitive. * Instead the receiver will have to rely on the BSSID to find the right * wifi AP to join. Lots of QR Scanners are buggy and do not respect * custom URI schemes, so we have to use http:// or https:// :-( */ final String qrUriString = Utils.getSharingUri(this, FDroidApp.repo).toString() .replaceFirst("fdroidrepo", "http") .replaceAll("ssid=[^?]*", "") .toUpperCase(Locale.ENGLISH); Log.i("QRURI", qrUriString); if (Build.VERSION.SDK_INT >= 8) // zxing requires >= 8 new QrGenAsyncTask(this, R.id.repoQrCode).execute(qrUriString); TextView wifiNetworkNameTextView = (TextView) findViewById(R.id.wifi_network); wifiNetworkNameTextView.setText(FDroidApp.ssid); TextView fingerprintTextView = (TextView) findViewById(R.id.fingerprint); if (FDroidApp.repo.fingerprint != null) { fingerprintTextView.setVisibility(View.VISIBLE); fingerprintTextView.setText(FDroidApp.repo.fingerprint); } else { fingerprintTextView.setVisibility(View.GONE); } // the required NFC API was added in 4.0 aka Ice Cream Sandwich if (Build.VERSION.SDK_INT >= 14) { NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this); if (nfcAdapter == null) return; nfcAdapter.setNdefPushMessage(new NdefMessage(new NdefRecord[] { NdefRecord.createUri(Utils.getSharingUri(this, FDroidApp.repo)), }), this); } } @Override public void onConfigurationChanged(Configuration newConfig) { // ignore orientation/keyboard change super.onConfigurationChanged(newConfig); } class UpdateAsyncTask extends AsyncTask { private static final String TAG = "UpdateAsyncTask"; private ProgressDialog progressDialog; private String[] selectedApps; private Uri sharingUri; public UpdateAsyncTask(Context c, String[] apps) { selectedApps = apps; progressDialog = new ProgressDialog(c); progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); progressDialog.setTitle(R.string.updating); sharingUri = Utils.getSharingUri(c, FDroidApp.repo); } @Override protected void onPreExecute() { progressDialog.show(); } @Override protected Void doInBackground(Void... params) { try { publishProgress(getString(R.string.deleting_repo)); FDroidApp.localRepo.deleteRepo(); for (String app : selectedApps) { publishProgress(String.format(getString(R.string.adding_apks_format), app)); FDroidApp.localRepo.addApp(getApplicationContext(), app); } FDroidApp.localRepo.writeIndexPage(sharingUri.toString()); publishProgress(getString(R.string.writing_index_xml)); FDroidApp.localRepo.writeIndexXML(); publishProgress(getString(R.string.linking_apks)); FDroidApp.localRepo.copyApksToRepo(); publishProgress(getString(R.string.copying_icons)); // run the icon copy without progress, its not a blocker new AsyncTask() { @Override protected Void doInBackground(Void... params) { FDroidApp.localRepo.copyIconsToRepo(); return null; } }.execute(); } catch (Exception e) { e.printStackTrace(); } return null; } @Override protected void onProgressUpdate(String... progress) { super.onProgressUpdate(progress); progressDialog.setMessage(progress[0]); } @Override protected void onPostExecute(Void result) { progressDialog.dismiss(); Toast.makeText(getBaseContext(), R.string.updated_local_repo, Toast.LENGTH_SHORT) .show(); } } }