يتمتع نظام تشغيل أندرويد بوسائل أمان خاصة كي تحمي مستخدميه من مخاطر سرقة البيانات أو العبث في الهاتف دون علم المستخدم، وذلك عن طريق عزل كل تطبيق داخل صندوق خاص به لا يستطيع أن يرى أو يتعامل مع الموارد الخاصة بالتطبيقات الأخرى ما لم تكن لديه الأذونات الخاصة لذلك وموافقة المستخدم عليها.
بشكل افتراضي لا يملك التطبيق أي أذونات للوصول لأي شيء خارج التطبيق كاستخدام الكاميرا أو الاتصال بأحد جهات الاتصال في الهاتف ما لم يتم تضمين الأذونات الخاصة بذلك داخل التطبيق ثم موافقة المستخدم عليه.
فعند تنصيب التطبيق من متجر Play يطلب من المستخدم الموافقة على أذونات محددة يحتاجها التطبيق كي يعمل وبعد الموافقة عليها يبدأ التطبيق في التحميل والتنصيب، لذا يُنصح دائمًا بقراءة هذه الأذونات بعناية والتأكد من احتياج التطبيق لها ليؤدي وظيفته المطلوبة دون الخوف من الوصول لأشياء لا يحتاجها وذلك لكثرة وجود البرمجيات الخبيثة التي تستغل موافقتك على الأذونات للعبث بموارد ومعلومات هاتفك.
تطبيق 1
في هذا المثال سنتعرف على كيفية صنع تطبيق يطلب الوصول إلى أذونات معينة من المستخدم كي يؤدي وظيفته، يقوم هذا التطبيق بدور المصباح اليدوي حيث يُضيء الضوء الخاص بالكاميرا (Flashlight) بشكل برمجي عند الضغط على زر بداخله.
كما نفعل دومًا نبدأ بصنع واجهة المستخدم الخاصة بالتطبيق، وتتكون من زر من النوع ImageButton
-وهو زر يستطيع أن يحتوي على صورة -لتصبح الشيفرة الخاصة بملف activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" > <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/power_on" android:id="@+id/btn" android:background="@null"/> </LinearLayout>
بعد ذلك نقوم بالتعديل في ملف AndroidManifest.xml
وإضافة الأذونات المطلوبة لكي نستطيع التحكم في ضوء الكاميرا.
<uses-permission android:name="android.permission.CAMERA"/> <uses-feature android:name="android.hardware.Camera"/>
باستخدام الوسم الخاص بالأذونات <uses-permissions>
وتحديد الاسم الخاص به في الخاصية android:name
، وأيضًا إضافة الوسم <uses-feature>
لإعلام الهاتف أن التطبيق يحتاج إلى وجود كاميرا كي يعمل.
ويوجد العديد من الأذونات التي تمكنك من القيام بالعديد من المهام من داخل التطبيق كالاتصال بالإنترنت أو القراءة والكتابة في الذاكرة ويمكنك التعرف عليها من هنا ويتم تعريفها داخل الملف AndroidManifest.xml
كما سبق.
إذا لم يتم تعريف الأذونات المستخدمة في التطبيق داخل ملف AndroidManifest.xml فإن التطبيق لن يعمل.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="apps.noby.flashlight"> <uses-permission android:name="android.permission.CAMERA"/> <uses-feature android:name="android.hardware.Camera"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
ننتقل الآن إلى كتابة شيفرة التحكم الخاصة بالتطبيق في ملف MainActivity.java
وتنقسم إلى
1-التأكد من توفر ضوء للكاميرا في الهاتف أولًا.
if(!getApplicationContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) finish();
وإذا لم يتواجد ضوء الكاميرا في الهاتف فسيتم غلق التطبيق في الحال.
2-الحصول على كائن يُمكننا من التحكم في ضوء الكاميرا وذلك عن طريق فتح الكاميرا ثم الحصول على الخصائص والمعاملات الخاصة بها وتغيير المتغير الخاص بضوء الكاميرا.
camera = Camera.open(); param = camera.getParameters();
3-برمجة الزر المتواجد في الواجهة الرئيسية لكي يُضيء ضوء الكاميرا عند الضغط عليه ويغلقه عن الضغط مرة أخرى.
btn = (ImageButton) findViewById(R.id.btn); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(!isTorchON){ turnONTorch(); }else turnOFFTorch(); } });
لتُصبح شيفرة التحكم الرئيسية هي
package apps.noby.flashlight; import android.hardware.Camera; import android.hardware.Camera.Parameters; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.ImageButton; public class MainActivity extends Activity { ImageButton btn; boolean isTorchON; Camera camera; private Parameters param; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn = (ImageButton) findViewById(R.id.btn); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(!isTorchON){ turnONTorch(); }else turnOFFTorch(); } }); } @Override protected void onStart() { super. onStart(); if(!getApplicationContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) finish(); if (camera == null) { isTorchON = false; camera = Camera.open(); param = camera.getParameters(); } } @Override protected void onPause() { super.onPause(); if (camera != null) { camera.release(); camera = null; } } }
يتبقى تعريف ما هي الوظيفة الخاصة بالدالة turnONTorch()
وTurnOFFTorch()
.
تقوم الدالة turnONTorch()
بتشغيل الضوء وذلك عن طريق تمرير المعامل FLASH_MODE_TORCH
كإحدى معاملات الكاميرا ثم تفعيل الكاميرا بالمعاملات الجديدة عند استدعاء startPreview()
.
private void turnONTorch() { param.setFlashMode(Parameters.FLASH_MODE_TORCH); camera.setParameters(param); camera.startPreview(); isTorchON = true; btn.setImageResource(R.drawable.power_off); }
وبالمثل نبني turnOFFTorch()
كي يتم إطفاء الضوء.
private void turnOFFTorch() { param.setFlashMode(Parameters.FLASH_MODE_OFF); camera.setParameters(param); camera.stopPreview(); isTorchON = false; btn.setImageResource(R.drawable.power_on); }
لتُصبح الشيفرة النهائية لملف MainActivity.java
package apps.noby.flashlight; import android.content.pm.PackageManager; import android.hardware.Camera; import android.hardware.Camera.Parameters; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.ImageButton; public class MainActivity extends Activity { ImageButton btn; boolean isTorchON; Camera camera; private Parameters param; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn = (ImageButton) findViewById(R.id.btn); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(!isTorchON){ turnONTorch(); }else turnOFFTorch(); } }); } private void turnONTorch() { param.setFlashMode(Parameters.FLASH_MODE_TORCH); camera.setParameters(param); camera.startPreview(); isTorchON = true; btn.setImageResource(R.drawable.power_off); } private void turnOFFTorch() { param.setFlashMode(Parameters.FLASH_MODE_OFF); camera.setParameters(param); camera.stopPreview(); isTorchON = false; btn.setImageResource(R.drawable.power_on); } @Override protected void onStart() { super. onStart(); if(! getApplicationContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) finish(); if (camera == null) { isTorchON = false; camera = Camera.open(); param = camera.getParameters(); } } @Override protected void onPause() { super.onPause(); if (camera != null) { camera.release(); camera = null; } } }
بعد ذلك نقوم بتجربة التطبيق على هاتف حقيقي وليس المحاكي حيث لا يمكنه محاكاة إضاءة ضوء الكاميرا.
تطبيق 2
بعد الإصدار السادس لنظام تشغيل أندرويد (Android Marshmallow) تم تعريف نموذج جديد للتعامل مع الأذونات داخل التطبيق حيث كان النموذج القديم يتطلب موافقة المستخدم على كافة الأذونات الخاصة بالتطبيق قبل تحميله على الهاتف أو رفضها وعدم تحميل التطبيق. أما الآن فأصبح بإمكان المستخدم تحميل وتنصيب التطبيق على الهاتف دون الموافقة على الأذونات إلا عند الحاجة إليها لكي تقوم بوظيفة ما، كما يمكن الموافقة على بعض الأذونات ورفض الأخرى (قد يؤدي ذلك إلى عدم عمل التطبيق بشكله الكامل) ويمكن بعد ذلك الموافقة عليها مما يعطي المستخدم القدرة على التحكم في التطبيق من داخل الإعدادات الخاصة بالتطبيق.
وتنقسم الأذونات إلى قسمين:
- الأذونات العادية وهي التي لا تهدد خصوصية المستخدم، ويتم التعامل معها دون طلب الأذن عند الحاجة إليها يكفي فقط طلب الأذن عند التحميل من المتجر مرة واحدة.
-
الأذونات الخطيرة وهي التي قد تصل إلى بيانات خاصة بالمستخدم وتحتاج إلى انتباه المستخدم للتطبيق وطلب الأذن منه عندما استخدامها من قبل التطبيق.
سنتعامل في هذا التطبيق مع أحد هذه الأذونات الخطيرة، حيث سنقوم بصنع تطبيق يمكنه الاتصال مباشرة برقم محدد من قبل المستخدم ويُعتبر الاتصال من الأذونات الخطيرة وذلك لأنه قد يُعرض المستخدم لتكاليف خاصة بالاتصال.
نبدأ أولًا بصنع واجهة المستخدم وهي تتكون من عنصر EditText كي يكتب المستخدم بداخله الرقم الذي يرغب في الاتصال به وزر للضغط عليه حتى يقوم التطبيق بالاتصال بهذا الرقم.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" android:layout_margin="16dp"> <EditText android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:hint="Enter Phone Number" android:id="@+id/ph_num"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Call" android:id="@+id/call_btn"/> </LinearLayout>
بعد ذلك نحدد الأذونات الخاصة بالتطبيق في AndroidManifest.xml
وهنا هو
<uses-permission android:name="android.permission.CALL_PHONE"/>
ويتم تضمينه داخل الملف كما سبق.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="apps.noby.dailer"> <uses-permission android:name="android.permission.CALL_PHONE"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
الآن يتبقى برمجة الزر كي يقوم بالاتصال ويتم التحكم في ذلك عن طريق استخدام Intent
وتمرير له ACTION_CALL
لتحديد وظيفته وهي الاتصال، ثم تحديد البيانات التي نرغب في إرسالها بداخل هذا الـ Intent وهي رقم الهاتف الذي تم إدخاله من قبل المستخدم داخل EditText
وتسبقه “tel:
” لإعلام نظام التشغيل بأنه رقم هاتف.
String num = et.getText().toString(); Intent intent =new Intent(Intent.ACTION_CALL); intent.setData(Uri.parse("tel:" + num)) ;
قبل القيام بعملية الاتصال يجب أولًا التأكد من حصول التطبيق على الأذونات الخاصة بالاتصال من المستخدم سواء عند التنصيب في الإصدارات القديمة أو طلبها في الإصدارات الأحدث.
call_btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String num = et.getText().toString(); Intent intent =new Intent(Intent.ACTION_CALL); intent.setData(Uri.parse("tel:" + num)) ; if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CALL_PHONE},CALL_PHONE_PERMISSIONS_REQUEST); } else startActivity(intent); } });
للتحقق من الأذونات نستخدم التابع checkSelfPermission()
فإذا كان لم يتم الحصول عليه نطلب من المستخدم الموافقة عليه باستخدام التابع requestPermissions()
غير ذلك نقوم بالاتصال.
عند طلب الحصول على إذن محدد من المستخدم يظهر أمام المستخدم مربع يطلب منه الموافقة أو الرفض ويقوم بتسجيل اختيار المستخدم لذلك ثم يستدعي التابع onRequestPermissionsResult()
بشكل تلقائي ونقوم بداخله من التأكد من موافقة المستخدم على الأذن وإظهار رسالة بذلك.
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { if (requestCode == CALL_PHONE_PERMISSIONS_REQUEST) { if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, "Call Phones permission granted", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "Call Phones permission denied", Toast.LENGTH_SHORT).show(); } } else { super.onRequestPermissionsResult(requestCode, permissions, grantResults); } } }
لتُصبح الشيفرة الكاملة لهذا التطبيق في ملف MainActivity.java
هي
package apps.noby.dailer; import android.Manifest; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; public class MainActivity extends Activity { private static final int CALL_PHONE_PERMISSIONS_REQUEST = 3; private EditText et; private Button call_btn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); et = (EditText) findViewById(R.id.ph_num); call_btn = (Button) findViewById(R.id.call_btn); call_btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String num = et.getText().toString(); Intent intent =new Intent(Intent.ACTION_CALL); intent.setData(Uri.parse("tel:" + num)) ; if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CALL_PHONE},CALL_PHONE_PERMISSIONS_REQUEST); } else startActivity(intent); } }); } @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { if (requestCode == CALL_PHONE_PERMISSIONS_REQUEST) { if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, "Call Phones permission granted", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "Call Phones permission denied", Toast.LENGTH_SHORT).show(); } } else { super.onRequestPermissionsResult(requestCode, permissions, grantResults); } } }
لاحظ أنه إذا كان الهاتف يعمل بإصدار سابق لـ Android Marshmallow فسيتم الحصول على الموافقة على كافة الأذونات عند التحميل من المتجر كما كان يحدث سابقًا.
يمكننا الآن تجربة التطبيق على المحاكي أو على هاتف حقيقي.
يجب عليك مراعاة عدم إجهاد المستخدم بالعديد من الأذونات التي لا حاجة لها داخل تطبيقك حتى لا يضطر المستخدم إلى إزالة تطبيقك والتخلص منه.
لذا يجب الاهتمام بتقديم تجربة استخدام جيدة، ففي العديد من الأحيان لا يكون خيار طلب الأذونات من المستخدم والقيام بالمهمة من داخل تطبيقك هو الخيار الأفضل، فقد يكون الأفضل استخدام الـ intent وطلب الوظيفة من تطبيق آخر للقيام بها.
مثال على ذلك يمكننا تعديل التطبيق السابق وإزالة الأذن الخاص بالاتصال من التطبيق، وتعديل الشيفرة في MainActivty.java كي تقوم بنقل مهمة الاتصال من التطبيق نفسه إلى أحد تطبيقات الاتصال المتواجدة بالهاتف
package apps.noby.dailer; import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; public class MainActivity extends Activity { private EditText et; private Button call_btn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); et = (EditText) findViewById(R.id.ph_num); call_btn = (Button) findViewById(R.id.call_btn); call_btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String num = et.getText().toString(); Intent intent =new Intent(Intent.ACTION_DIAL); intent.setData(Uri.parse("tel:" + num)) ; startActivity(intent); } }); } }
عند تجربة التطبيق على المحاكي ينقلنا إلى تطبيق الاتصال الخاص بالهاتف مع إدخال رقم الهاتف والانتظار لاتصال المستخدم بالرقم.
لا توجد طريقة أفضل من الأخرى، بل يعتمد ذلك على مدى اهتمامك بالحصول على التحكم الكامل بكافة التفاصيل لأداء المهمة عند ذلك قم بطلب الأذن من المستخدم وإذا رفض المستخدم ذلك فلن يعمل تطبيقك، أو ترك الوظيفة إلى تطبيق آخر وتكتفي فقط باستقبال أو إرسال البيانات منه أو إليه ولن تحتاج إلى الحصول على أي أذونات من المستخدم لكنك ستنقل المستخدم إلى تطبيق آخر لا تعلم عنه شيئًا قد يضر تجربة الاستخدام الخاصة بتطبيقك.
بهذا نكون قد وصلنا إلى نهاية هذا الدرس، في انتظار تجربتكم وآرائكم.
أفضل التعليقات
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.