Mam aplikację, która chce trafić na rynek jako aplikacja płatna. Chciałbym mieć inną wersję, która byłaby wersją „próbną” z ograniczeniem czasowym powiedzmy 5 dni?
Jak mam to zrobić?
Mam aplikację, która chce trafić na rynek jako aplikacja płatna. Chciałbym mieć inną wersję, która byłaby wersją „próbną” z ograniczeniem czasowym powiedzmy 5 dni?
Jak mam to zrobić?
Odpowiedzi:
Obecnie większość programistów dokonuje tego za pomocą jednej z trzech poniższych technik.
Pierwsze podejście można łatwo obejść, przy pierwszym uruchomieniu aplikacji zapisz datę / godzinę w pliku, bazie danych lub wspólnych preferencjach i za każdym razem, gdy uruchomisz aplikację po tym sprawdzeniu, czy okres próbny dobiegł końca. Można to łatwo obejść, ponieważ odinstalowanie i ponowna instalacja pozwoli użytkownikowi na kolejny okres próbny.
Drugie podejście jest trudniejsze do obejścia, ale nadal można je obejść. Użyj zakodowanej bomby zegarowej. Zasadniczo dzięki temu podejściu ustalisz na stałe datę zakończenia okresu próbnego, a wszyscy użytkownicy, którzy pobierają i używają aplikacji, przestaną być w stanie korzystać z aplikacji w tym samym czasie. Użyłem tego podejścia, ponieważ jest ono łatwe do wdrożenia i przeważnie nie miałem ochoty przechodzić przez kłopoty związane z trzecią techniką. Użytkownicy mogą to obejść, ręcznie zmieniając datę w telefonie, ale większość użytkowników nie będzie miała problemu z zrobieniem czegoś takiego.
Trzecia technika to jedyny sposób, o jakim słyszałem, aby naprawdę móc osiągnąć to, co chcesz. Będziesz musiał skonfigurować serwer, a następnie za każdym razem, gdy Twoja aplikacja jest uruchamiana, aplikacja wysyła unikalny identyfikator telefonu do serwera. Jeśli serwer nie ma wpisu dla tego identyfikatora telefonu, tworzy nowy i zapisuje godzinę. Jeśli serwer ma wpis dla identyfikatora telefonu, wykonuje proste sprawdzenie, czy okres próbny wygasł. Następnie przekazuje wyniki sprawdzenia wygaśnięcia okresu próbnego z powrotem do aplikacji. Tego podejścia nie można obejść, ale wymaga skonfigurowania serwera internetowego i tym podobnych.
Zawsze dobrze jest przeprowadzać te sprawdzenia w onCreate. Jeśli wygaśnięcie zakończyło się wyskakujące okienko AlertDialog z linkiem rynkowym do pełnej wersji aplikacji. Dołącz tylko przycisk „OK”, a gdy użytkownik kliknie „OK”, wywołaj funkcję „finish ()”, aby zakończyć działanie.
Opracowałem Android Trial SDK, który możesz po prostu wrzucić do swojego projektu Android Studio i zajmie się całym zarządzaniem po stronie serwera (w tym okresami prolongaty offline).
Aby z niego skorzystać, po prostu
Dodaj bibliotekę do głównego modułu build.gradle
dependencies {
compile 'io.trialy.library:trialy:1.0.2'
}
Zainicjuj bibliotekę w onCreate()
metodzie swojego głównego działania
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Initialize the library and check the current trial status on every launch
Trialy mTrialy = new Trialy(mContext, "YOUR_TRIALY_APP_KEY");
mTrialy.checkTrial(TRIALY_SKU, mTrialyCallback);
}
Dodaj moduł obsługi wywołań zwrotnych:
private TrialyCallback mTrialyCallback = new TrialyCallback() {
@Override
public void onResult(int status, long timeRemaining, String sku) {
switch (status){
case STATUS_TRIAL_JUST_STARTED:
//The trial has just started - enable the premium features for the user
break;
case STATUS_TRIAL_RUNNING:
//The trial is currently running - enable the premium features for the user
break;
case STATUS_TRIAL_JUST_ENDED:
//The trial has just ended - block access to the premium features
break;
case STATUS_TRIAL_NOT_YET_STARTED:
//The user hasn't requested a trial yet - no need to do anything
break;
case STATUS_TRIAL_OVER:
//The trial is over
break;
}
Log.i("TRIALY", "Trialy response: " + Trialy.getStatusMessage(status));
}
};
Aby rozpocząć okres próbny, zadzwoń do mTrialy.startTrial("YOUR_TRIAL_SKU", mTrialyCallback);
swojego klucza aplikacji, a wersję próbną SKU można znaleźć na pulpicie programisty Trialy .
To stare pytanie, ale może komuś to pomoże.
Jeśli chcesz zastosować najbardziej uproszczone podejście (które się nie powiedzie, jeśli aplikacja zostanie odinstalowana / ponownie zainstalowana lub użytkownik ręcznie zmieni datę urządzenia), może to wyglądać następująco:
private final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
private final long ONE_DAY = 24 * 60 * 60 * 1000;
@Override
protected void onCreate(Bundle state){
SharedPreferences preferences = getPreferences(MODE_PRIVATE);
String installDate = preferences.getString("InstallDate", null);
if(installDate == null) {
// First run, so save the current date
SharedPreferences.Editor editor = preferences.edit();
Date now = new Date();
String dateString = formatter.format(now);
editor.putString("InstallDate", dateString);
// Commit the edits!
editor.commit();
}
else {
// This is not the 1st run, check install date
Date before = (Date)formatter.parse(installDate);
Date now = new Date();
long diff = now.getTime() - before.getTime();
long days = diff / ONE_DAY;
if(days > 30) { // More than 30 days?
// Expired !!!
}
}
...
}
getTime
nie jest getTimeInMillis
.
To pytanie i odpowiedź snctln zainspirowały mnie do pracy nad rozwiązaniem opartym na metodzie 3 jako moja praca licencjacka. Wiem, że obecny status nie służy do produktywnego użytku, ale chciałbym usłyszeć, co o nim myślisz! Czy użyłbyś takiego systemu? Czy chciałbyś widzieć to jako usługę w chmurze (nie masz problemu z konfiguracją serwera)? Martwisz się o kwestie bezpieczeństwa lub przyczyny związane ze stabilnością?
Zaraz po zakończeniu procedury licencjackiej chcę kontynuować pracę nad oprogramowaniem. Więc teraz czas na twoją opinię!
Kod źródłowy jest hostowany na GitHub https://github.com/MaChristmann/mobile-trial
Kilka informacji o systemie: - System składa się z trzech części, biblioteki Android, serwera node.js i konfiguratora do zarządzania wieloma aplikacjami próbnymi i kontami wydawcy / programisty.
Obsługuje tylko okresowe wersje próbne i używa konta (sklepu Play lub innego) zamiast identyfikatora telefonu.
W przypadku biblioteki Android jest ona oparta na bibliotece weryfikacji licencji Google Play. Zmodyfikowałem go, aby połączyć się z serwerem node.js i dodatkowo biblioteka próbuje rozpoznać, czy użytkownik zmienił datę systemową. Buforuje również pobraną licencję próbną w preferencjach wspólnych z szyfrowaniem AES. Za pomocą konfiguratora możesz skonfigurować aktualny czas pamięci podręcznej. Jeśli użytkownik „wyczyści dane”, biblioteka wymusi sprawdzenie po stronie serwera.
Serwer używa protokołu HTTPS, a także podpisuje cyfrowo odpowiedź sprawdzającą licencję. Posiada również API dla aplikacji próbnych CRUD i użytkowników (wydawcy i programisty). Podobnie jak w przypadku Biblioteki weryfikacji licencji, programiści mogą testować implementację swojego zachowania w aplikacji próbnej z wynikiem testu. Dlatego w konfiguratorze możesz jawnie ustawić odpowiedź licencji na „licencjonowana”, „nie licencjonowana” lub „błąd serwera”.
Jeśli zaktualizujesz swoją aplikację za pomocą niesamowitej nowej funkcji, możesz chcieć, aby wszyscy mogli spróbować ponownie. W konfiguratorze możesz odnowić licencję próbną dla użytkowników z wygasłymi licencjami, ustawiając kod wersji, który powinien to wywołać. Na przykład użytkownik uruchamia Twoją aplikację z kodem wersji 3 i chcesz, aby wypróbował funkcje kodu wersji 4. Jeśli zaktualizuje aplikację lub ją ponownie zainstaluje, będzie mógł ponownie skorzystać z pełnego okresu próbnego, ponieważ serwer wie, na której wersji ostatnio ją wypróbował czas.
Wszystko jest objęte licencją Apache 2.0
Najłatwiejszym i najlepszym sposobem na to jest zaimplementowanie BackupSharedPreferences.
Preferencje są zachowywane, nawet jeśli aplikacja zostanie odinstalowana i ponownie zainstalowana.
Po prostu zapisz datę instalacji jako preferencję i gotowe.
Oto teoria: http://developer.android.com/reference/android/app/backup/SharedPreferencesBackupHelper.html
Oto przykład: Kopia zapasowa SharedPreferences systemu Android nie działa
Podejście 4: użyj czasu instalacji aplikacji.
Od poziomu 9 interfejsu API (Android 2.3.2, 2.3.1, Android 2.3, GINGERBREAD ) istnieją firstInstallTime i lastUpdateTime w PackageInfo
.
Więcej informacji: Jak uzyskać czas instalacji aplikacji z Androida
Teraz, w najnowszej wersji bezpłatnej subskrypcji próbnej Androida, możesz odblokować wszystkie funkcje aplikacji dopiero po wykupieniu subskrypcji w aplikacji na bezpłatny okres próbny. Pozwoli to użytkownikowi korzystać z Twojej aplikacji przez okres próbny, jeśli aplikacja będzie nadal odinstalowana po okresie próbnym, pieniądze z subskrypcji zostaną przesłane do Ciebie. Nie próbowałem, po prostu dzielę się pomysłem.
Moim zdaniem najlepszym sposobem na to jest po prostu skorzystanie z bazy danych czasu rzeczywistego Firebase:
1) Dodaj obsługę Firebase do swojej aplikacji
2) Wybierz „Uwierzytelnianie anonimowe”, aby użytkownik nie musiał się rejestrować ani nawet wiedzieć, co robisz. To gwarantuje połączenie z aktualnie uwierzytelnionym kontem użytkownika i będzie działać na różnych urządzeniach.
3) Użyj interfejsu API bazy danych czasu rzeczywistego, aby ustawić wartość parametru „installed_date”. W czasie uruchamiania po prostu pobierz tę wartość i użyj jej.
Zrobiłem to samo i działa świetnie. Udało mi się to przetestować podczas odinstalowywania / ponownej instalacji, a wartość w bazie danych czasu rzeczywistego pozostaje taka sama. W ten sposób okres próbny działa na wielu urządzeniach użytkowników. Możesz nawet ustawić wersję swojej install_date, aby aplikacja `` resetowała '' datę wersji próbnej dla każdej nowej wersji głównej.
AKTUALIZACJA : po przeprowadzeniu dalszych testów wydaje się, że wydaje się, że Firebase anonimowo przydziela inny identyfikator w przypadku, gdy masz różne urządzenia i nie ma gwarancji między kolejnymi instalacjami: / Jedynym gwarantowanym sposobem jest użycie Firebase, ale powiązanie go z ich Google konto. To powinno działać, ale wymagałoby dodatkowego kroku, w którym użytkownik musi najpierw zalogować się / zarejestrować.
Jak dotąd skończyłem z nieco mniej eleganckim podejściem, polegającym na prostym sprawdzaniu kopii zapasowej preferencji i daty zapisanej w preferencjach podczas instalacji. Działa to w przypadku aplikacji zorientowanych na dane, w przypadku których nie ma sensu ponowne instalowanie aplikacji i ponowne wprowadzanie wszystkich wcześniej dodanych danych, ale nie działa to w przypadku prostej gry.
Po przejrzeniu wszystkich opcji w tym i innych wątkach, oto moje ustalenia
Wspólne preferencje, baza danych Można wyczyścić w ustawieniach Androida, utracić po ponownej instalacji aplikacji. Można zarchiwizować za pomocą mechanizmu tworzenia kopii zapasowych Androida i zostanie przywrócona po ponownej instalacji. Kopia zapasowa może nie zawsze być dostępna, chociaż powinna znajdować się na większości urządzeń
Pamięć zewnętrzna (zapis do pliku) Nie dotyczy wyczyszczenia ustawień lub ponownej instalacji, jeśli nie zapisujemy w prywatnym katalogu aplikacji . Ale: wymaga, abyś poprosił użytkownika o pozwolenie w czasie wykonywania w nowszych wersjach Androida, więc jest to prawdopodobnie możliwe tylko wtedy, gdy i tak potrzebujesz tego uprawnienia. Można również zarchiwizować.
PackageInfo.firstInstallTime jest resetowany po ponownej instalacji, ale stabilny między aktualizacjami
Zaloguj się na jakieś konto Nie ma znaczenia, czy jest to konto Google przez Firebase, czy jedno na Twoim własnym serwerze: wersja próbna jest powiązana z kontem. Utworzenie nowego konta spowoduje zresetowanie okresu próbnego.
Anonimowe logowanie do Firebase Możesz zalogować użytkownika anonimowo i przechowywać jego dane w Firebase. Ale najwyraźniej ponowna instalacja aplikacji i być może inne nieudokumentowane zdarzenia mogą dać użytkownikowi nowy anonimowy identyfikator , resetując jego czas próbny. (Same Google nie dostarczają dużo dokumentacji na ten temat)
ANDROID_ID Może być niedostępny i może ulec zmianie w pewnych okolicznościach , np. Przywrócenie ustawień fabrycznych. Opinie na temat tego, czy warto używać tego do identyfikacji urządzeń, wydają się różne.
Identyfikator reklamowy Play Może zostać zresetowany przez użytkownika. Może zostać wyłączone przez użytkownika poprzez rezygnację ze śledzenia reklam.
Reset ID instancji podczas ponownej instalacji . Zresetuj w przypadku zdarzenia związanego z bezpieczeństwem. Można zresetować za pomocą aplikacji.
To, która (kombinacja) metod działa w Twoim przypadku, zależy od Twojej aplikacji i od tego, ile wysiłku, według Ciebie, przeciętny Jan włoży w uzyskanie kolejnego okresu próbnego. Zalecałbym unikanie używania tylko anonimowych identyfikatorów Firebase i Advertising ID ze względu na ich niestabilność. Wydaje się, że podejście wieloczynnikowe przyniesie najlepsze rezultaty. Dostępność dostępnych czynników zależy od aplikacji i jej uprawnień.
W przypadku mojej własnej aplikacji okazało się, że wspólne preferencje + firstInstallTime + kopia zapasowa preferencji są najmniej uciążliwą, ale także wystarczająco skuteczną metodą. Musisz upewnić się, że zażądasz kopii zapasowej tylko po sprawdzeniu i zapisaniu czasu rozpoczęcia okresu próbnego we wspólnych preferencjach. Wartości we wspólnych prefs muszą mieć pierwszeństwo przed firstInstallTime. Następnie użytkownik musi ponownie zainstalować aplikację, uruchomić ją raz, a następnie wyczyścić dane aplikacji, aby zresetować wersję próbną, co jest dość dużo pracy. Jednak na urządzeniach bez zapasowego transportu użytkownik może zresetować wersję próbną, po prostu przeinstalowując.
Udostępniłem to podejście jako rozszerzalną bibliotekę .
Z definicji wszystkie płatne aplikacje na Androida dostępne na rynku można oceniać przez 24 godziny od zakupu.
Dostępny jest przycisk „Odinstaluj i zwróć środki”, który po 24 godzinach zmienia się na „Odinstaluj”.
Twierdzę, że ten przycisk jest zbyt widoczny!
Natknąłem się na to pytanie, szukając tego samego problemu, myślę, że możemy skorzystać z darmowego interfejsu API dat, takiego jak http://www.timeapi.org/utc/now lub innego interfejsu API do sprawdzania daty wygaśnięcia aplikacji. ten sposób jest skuteczny, jeśli chcesz dostarczyć wersję demonstracyjną i martwisz się o płatność i potrzebujesz wersji demo z ustalonym okresem zatrudnienia. :)
znajdź kod poniżej
public class ValidationActivity extends BaseMainActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onResume() {
processCurrentTime();
super.onResume();
}
private void processCurrentTime() {
if (!isDataConnectionAvailable(ValidationActivity.this)) {
showerrorDialog("No Network coverage!");
} else {
String urlString = "http://api.timezonedb.com/?zone=Europe/London&key=OY8PYBIG2IM9";
new CallAPI().execute(urlString);
}
}
private void showerrorDialog(String data) {
Dialog d = new Dialog(ValidationActivity.this);
d.setTitle("LS14");
TextView tv = new TextView(ValidationActivity.this);
tv.setText(data);
tv.setPadding(20, 30, 20, 50);
d.setContentView(tv);
d.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
finish();
}
});
d.show();
}
private void checkExpiry(int isError, long timestampinMillies) {
long base_date = 1392878740000l;// feb_19 13:8 in GMT;
// long expiryInMillies=1000*60*60*24*5;
long expiryInMillies = 1000 * 60 * 10;
if (isError == 1) {
showerrorDialog("Server error, please try again after few seconds");
} else {
System.out.println("fetched time " + timestampinMillies);
System.out.println("system time -" + (base_date + expiryInMillies));
if (timestampinMillies > (base_date + expiryInMillies)) {
showerrorDialog("Demo version expired please contact vendor support");
System.out.println("expired");
}
}
}
private class CallAPI extends AsyncTask<String, String, String> {
@Override
protected void onPreExecute() {
// TODO Auto-generated method stub
super.onPreExecute();
}
@Override
protected String doInBackground(String... params) {
String urlString = params[0]; // URL to call
String resultToDisplay = "";
InputStream in = null;
// HTTP Get
try {
URL url = new URL(urlString);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream());
resultToDisplay = convertStreamToString(in);
} catch (Exception e) {
System.out.println(e.getMessage());
return e.getMessage();
}
return resultToDisplay;
}
protected void onPostExecute(String result) {
int isError = 1;
long timestamp = 0;
if (result == null || result.length() == 0 || result.indexOf("<timestamp>") == -1 || result.indexOf("</timestamp>") == -1) {
System.out.println("Error $$$$$$$$$");
} else {
String strTime = result.substring(result.indexOf("<timestamp>") + 11, result.indexOf("</timestamp>"));
System.out.println(strTime);
try {
timestamp = Long.parseLong(strTime) * 1000;
isError = 0;
} catch (NumberFormatException ne) {
}
}
checkExpiry(isError, timestamp);
}
} // end CallAPI
public static boolean isDataConnectionAvailable(Context context) {
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = connectivityManager.getActiveNetworkInfo();
if (info == null)
return false;
return connectivityManager.getActiveNetworkInfo().isConnected();
}
public String convertStreamToString(InputStream is) throws IOException {
if (is != null) {
Writer writer = new StringWriter();
char[] buffer = new char[1024];
try {
Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
int n;
while ((n = reader.read(buffer)) != -1) {
writer.write(buffer, 0, n);
}
} finally {
is.close();
}
return writer.toString();
} else {
return "";
}
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
jego działające rozwiązanie .....
Oto jak poszedłem do mojego, stworzyłem 2 aplikacje, jedną z próbną aktywnością, a drugą bez,
przesłałem ten bez aktywności próbnej do sklepu Play jako płatną aplikację,
i ten z aktywnością próbną jako darmową aplikacją.
Bezpłatna aplikacja przy pierwszym uruchomieniu ma opcje zakupu wersji próbnej i zakupu w sklepie, jeśli użytkownik wybierze zakup w sklepie, przekierowuje do sklepu, aby użytkownik mógł dokonać zakupu, ale jeśli użytkownik kliknie wersję próbną, przeniesie go do aktywności próbnej
NB: użyłem opcji 3, jak @snctln, ale z modyfikacjami
po pierwsze , nie zależałem od czasu urządzenia, mam swój czas z pliku php, który wykonuje próbną rejestrację do bazy danych,
po drugie , użyłem numeru seryjnego urządzenia, aby jednoznacznie zidentyfikować każde urządzenie,
wreszcie aplikacja zależy od wartości czasu zwróconej z połączenia z serwerem, a nie od własnego czasu, więc system można obejść tylko wtedy, gdy numer seryjny urządzenia zostanie zmieniony, co jest dość stresujące dla użytkownika.
więc oto mój kod (dla działania próbnego):
package com.example.mypackage.my_app.Start_Activity.activity;
import android.Manifest;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.telephony.TelephonyManager;
import android.view.KeyEvent;
import android.widget.TextView;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;
import com.example.onlinewisdom.cbn_app.R;
import com.example.mypackage.my_app.Start_Activity.app.Config;
import com.example.mypackage.my_app.Start_Activity.data.TrialData;
import com.example.mypackage.my_app.Start_Activity.helper.connection.Connection;
import com.google.gson.Gson;
import org.json.JSONObject;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import cn.pedant.SweetAlert.SweetAlertDialog;
public class Trial extends AppCompatActivity {
Connection check;
SweetAlertDialog pDialog;
TextView tvPleaseWait;
private static final int MY_PERMISSIONS_REQUEST_READ_PHONE_STATE = 0;
String BASE_URL = Config.BASE_URL;
String BASE_URL2 = BASE_URL+ "/register_trial/"; //http://ur link to ur API
//KEY
public static final String KEY_IMEI = "IMEINumber";
private final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
private final long ONE_DAY = 24 * 60 * 60 * 1000;
SharedPreferences preferences;
String installDate;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_trial);
preferences = getPreferences(MODE_PRIVATE);
installDate = preferences.getString("InstallDate", null);
pDialog = new SweetAlertDialog(this, SweetAlertDialog.PROGRESS_TYPE);
pDialog.getProgressHelper().setBarColor(Color.parseColor("#008753"));
pDialog.setTitleText("Loading...");
pDialog.setCancelable(false);
tvPleaseWait = (TextView) findViewById(R.id.tvPleaseWait);
tvPleaseWait.setText("");
if(installDate == null) {
//register app for trial
animateLoader(true);
CheckConnection();
} else {
//go to main activity and verify there if trial period is over
Intent i = new Intent(Trial.this, MainActivity.class);
startActivity(i);
// close this activity
finish();
}
}
public void CheckConnection() {
check = new Connection(this);
if (check.isConnected()) {
//trigger 'loadIMEI'
loadIMEI();
} else {
errorAlert("Check Connection", "Network is not detected");
tvPleaseWait.setText("Network is not detected");
animateLoader(false);
}
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
//Changes 'back' button action
if (keyCode == KeyEvent.KEYCODE_BACK) {
finish();
}
return true;
}
public void animateLoader(boolean visibility) {
if (visibility)
pDialog.show();
else
pDialog.hide();
}
public void errorAlert(String title, String msg) {
new SweetAlertDialog(this, SweetAlertDialog.ERROR_TYPE)
.setTitleText(title)
.setContentText(msg)
.show();
}
/**
* Called when the 'loadIMEI' function is triggered.
*/
public void loadIMEI() {
// Check if the READ_PHONE_STATE permission is already available.
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
// READ_PHONE_STATE permission has not been granted.
requestReadPhoneStatePermission();
} else {
// READ_PHONE_STATE permission is already been granted.
doPermissionGrantedStuffs();
}
}
/**
* Requests the READ_PHONE_STATE permission.
* If the permission has been denied previously, a dialog will prompt the user to grant the
* permission, otherwise it is requested directly.
*/
private void requestReadPhoneStatePermission() {
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.READ_PHONE_STATE)) {
// Provide an additional rationale to the user if the permission was not granted
// and the user would benefit from additional context for the use of the permission.
// For example if the user has previously denied the permission.
new AlertDialog.Builder(Trial.this)
.setTitle("Permission Request")
.setMessage(getString(R.string.permission_read_phone_state_rationale))
.setCancelable(false)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
//re-request
ActivityCompat.requestPermissions(Trial.this,
new String[]{Manifest.permission.READ_PHONE_STATE},
MY_PERMISSIONS_REQUEST_READ_PHONE_STATE);
}
})
.setIcon(R.drawable.warning_sigh)
.show();
} else {
// READ_PHONE_STATE permission has not been granted yet. Request it directly.
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_PHONE_STATE},
MY_PERMISSIONS_REQUEST_READ_PHONE_STATE);
}
}
/**
* Callback received when a permissions request has been completed.
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == MY_PERMISSIONS_REQUEST_READ_PHONE_STATE) {
// Received permission result for READ_PHONE_STATE permission.est.");
// Check if the only required permission has been granted
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// READ_PHONE_STATE permission has been granted, proceed with displaying IMEI Number
//alertAlert(getString(R.string.permision_available_read_phone_state));
doPermissionGrantedStuffs();
} else {
alertAlert(getString(R.string.permissions_not_granted_read_phone_state));
}
}
}
private void alertAlert(String msg) {
new AlertDialog.Builder(Trial.this)
.setTitle("Permission Request")
.setMessage(msg)
.setCancelable(false)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// do somthing here
}
})
.setIcon(R.drawable.warning_sigh)
.show();
}
private void successAlert(String msg) {
new SweetAlertDialog(this, SweetAlertDialog.SUCCESS_TYPE)
.setTitleText("Success")
.setContentText(msg)
.setConfirmText("Ok")
.setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() {
@Override
public void onClick(SweetAlertDialog sDialog) {
sDialog.dismissWithAnimation();
// Prepare intent which is to be triggered
//Intent i = new Intent(Trial.this, MainActivity.class);
//startActivity(i);
}
})
.show();
}
public void doPermissionGrantedStuffs() {
//Have an object of TelephonyManager
TelephonyManager tm =(TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
//Get IMEI Number of Phone //////////////// for this example i only need the IMEI
String IMEINumber = tm.getDeviceId();
/************************************************
* **********************************************
* This is just an icing on the cake
* the following are other children of TELEPHONY_SERVICE
*
//Get Subscriber ID
String subscriberID=tm.getDeviceId();
//Get SIM Serial Number
String SIMSerialNumber=tm.getSimSerialNumber();
//Get Network Country ISO Code
String networkCountryISO=tm.getNetworkCountryIso();
//Get SIM Country ISO Code
String SIMCountryISO=tm.getSimCountryIso();
//Get the device software version
String softwareVersion=tm.getDeviceSoftwareVersion()
//Get the Voice mail number
String voiceMailNumber=tm.getVoiceMailNumber();
//Get the Phone Type CDMA/GSM/NONE
int phoneType=tm.getPhoneType();
switch (phoneType)
{
case (TelephonyManager.PHONE_TYPE_CDMA):
// your code
break;
case (TelephonyManager.PHONE_TYPE_GSM)
// your code
break;
case (TelephonyManager.PHONE_TYPE_NONE):
// your code
break;
}
//Find whether the Phone is in Roaming, returns true if in roaming
boolean isRoaming=tm.isNetworkRoaming();
if(isRoaming)
phoneDetails+="\nIs In Roaming : "+"YES";
else
phoneDetails+="\nIs In Roaming : "+"NO";
//Get the SIM state
int SIMState=tm.getSimState();
switch(SIMState)
{
case TelephonyManager.SIM_STATE_ABSENT :
// your code
break;
case TelephonyManager.SIM_STATE_NETWORK_LOCKED :
// your code
break;
case TelephonyManager.SIM_STATE_PIN_REQUIRED :
// your code
break;
case TelephonyManager.SIM_STATE_PUK_REQUIRED :
// your code
break;
case TelephonyManager.SIM_STATE_READY :
// your code
break;
case TelephonyManager.SIM_STATE_UNKNOWN :
// your code
break;
}
*/
// Now read the desired content to a textview.
//tvPleaseWait.setText(IMEINumber);
UserTrialRegistrationTask(IMEINumber);
}
/**
* Represents an asynchronous login task used to authenticate
* the user.
*/
private void UserTrialRegistrationTask(final String IMEINumber) {
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET, BASE_URL2+IMEINumber, null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
Gson gson = new Gson();
TrialData result = gson.fromJson(String.valueOf(response), TrialData.class);
animateLoader(false);
if ("true".equals(result.getError())) {
errorAlert("Error", result.getResult());
tvPleaseWait.setText("Unknown Error");
} else if ("false".equals(result.getError())) {
//already created install/trial_start date using the server
// so just getting the date called back
Date before = null;
try {
before = (Date)formatter.parse(result.getResult());
} catch (ParseException e) {
e.printStackTrace();
}
Date now = new Date();
assert before != null;
long diff = now.getTime() - before.getTime();
long days = diff / ONE_DAY;
// save the date received
SharedPreferences.Editor editor = preferences.edit();
editor.putString("InstallDate", String.valueOf(days));
// Commit the edits!
editor.apply();
//go to main activity and verify there if trial period is over
Intent i = new Intent(Trial.this, MainActivity.class);
startActivity(i);
// close this activity
finish();
//successAlert(String.valueOf(days));
//if(days > 5) { // More than 5 days?
// Expired !!!
//}
}
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
animateLoader(false);
//errorAlert(error.toString());
errorAlert("Check Connection", "Could not establish a network connection.");
tvPleaseWait.setText("Network is not detected");
}
})
{
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<String, String>();
params.put(KEY_IMEI, IMEINumber);
return params;
}
};
RequestQueue requestQueue = Volley.newRequestQueue(this);
requestQueue.add(jsonObjectRequest);
}
}
Mój plik php wygląda następująco (jest to technologia REST-slim):
/**
* registerTrial
*/
public function registerTrial($IMEINumber) {
//check if $IMEINumber already exist
// Instantiate DBH
$DBH = new PDO_Wrapper();
$DBH->query("SELECT date_reg FROM trials WHERE device_id = :IMEINumber");
$DBH->bind(':IMEINumber', $IMEINumber);
// DETERMINE HOW MANY ROWS OF RESULTS WE GOT
$totalRows_registered = $DBH->rowCount();
// DETERMINE HOW MANY ROWS OF RESULTS WE GOT
$results = $DBH->resultset();
if (!$IMEINumber) {
return 'Device serial number could not be determined.';
} else if ($totalRows_registered > 0) {
$results = $results[0];
$results = $results['date_reg'];
return $results;
} else {
// Instantiate variables
$trial_unique_id = es_generate_guid(60);
$time_reg = date('H:i:s');
$date_reg = date('Y-m-d');
$DBH->beginTransaction();
// opening db connection
//NOW Insert INTO DB
$DBH->query("INSERT INTO trials (time_reg, date_reg, date_time, device_id, trial_unique_id) VALUES (:time_reg, :date_reg, NOW(), :device_id, :trial_unique_id)");
$arrayValue = array(':time_reg' => $time_reg, ':date_reg' => $date_reg, ':device_id' => $IMEINumber, ':trial_unique_id' => $trial_unique_id);
$DBH->bindArray($arrayValue);
$subscribe = $DBH->execute();
$DBH->endTransaction();
return $date_reg;
}
}
następnie w głównym działaniu używam preferencji współdzielonych (installDate utworzona w działaniu próbnym), aby monitorować liczbę pozostałych dni, a jeśli dni się skończyły, blokuję główny interfejs aktywności za pomocą komunikatu, który prowadzi ich do sklepu w celu zakupu.
Jedyną wadą, jaką tutaj widzę, jest to, że jeśli użytkownik Rogue kupi płatną aplikację i zdecyduje się udostępnić ją aplikacjom takim jak Zender, udostępnić plik lub nawet hostować plik apk bezpośrednio na serwerze, aby ludzie mogli go pobrać za darmo. Ale jestem pewien, że wkrótce edytuję tę odpowiedź, podając rozwiązanie tego problemu lub link do rozwiązania.
Mam nadzieję, że to uratuje duszę ... któregoś dnia
Miłego kodowania ...
@snctln opcję 3 można łatwo zrobić, dodając plik php do serwera internetowego z zainstalowanymi php i mysql, tak jak wiele z nich ma.
Ze strony Androida identyfikator (identyfikator urządzenia, konto Google lub cokolwiek chcesz) jest przekazywany jako argument w adresie URL za pomocą HttpURLConnection, a php zwraca datę pierwszej instalacji, jeśli istnieje w tabeli lub wstawia nowy wiersz i zwraca aktualną datę.
U mnie działa dobrze.
Jeśli będę miał czas, wyślę kod!
Powodzenia !