Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
P
platform_packages_modules_Connectivity
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Iterations
Wiki
Requirements
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Test cases
Artifacts
Deploy
Releases
Package Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Issue analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
LMODroid
platform_packages_modules_Connectivity
Commits
366eae57
Commit
366eae57
authored
4 years ago
by
Treehugger Robot
Committed by
Gerrit Code Review
4 years ago
Browse files
Options
Downloads
Plain Diff
Merge "TetheringServiceTest: test caller permission"
parents
fb2caff5
2a1656d3
No related branches found
Branches containing commit
No related tags found
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
Tethering/tests/unit/AndroidManifest.xml
+0
-3
0 additions, 3 deletions
Tethering/tests/unit/AndroidManifest.xml
Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+313
-37
313 additions, 37 deletions
.../android/networkstack/tethering/TetheringServiceTest.java
with
313 additions
and
40 deletions
Tethering/tests/unit/AndroidManifest.xml
+
0
−
3
View file @
366eae57
...
...
@@ -16,9 +16,6 @@
<manifest
xmlns:android=
"http://schemas.android.com/apk/res/android"
package=
"com.android.networkstack.tethering.tests.unit"
>
<uses-permission
android:name=
"android.permission.READ_PHONE_STATE"
/>
<uses-permission
android:name=
"android.permission.TETHER_PRIVILEGED"
/>
<application
android:debuggable=
"true"
>
<uses-library
android:name=
"android.test.runner"
/>
<service
...
...
This diff is collapsed.
Click to expand it.
Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+
313
−
37
View file @
366eae57
...
...
@@ -16,20 +16,29 @@
package
com.android.networkstack.tethering
;
import
static
android
.
Manifest
.
permission
.
ACCESS_NETWORK_STATE
;
import
static
android
.
Manifest
.
permission
.
TETHER_PRIVILEGED
;
import
static
android
.
Manifest
.
permission
.
WRITE_SETTINGS
;
import
static
android
.
net
.
TetheringManager
.
TETHERING_WIFI
;
import
static
android
.
net
.
TetheringManager
.
TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION
;
import
static
android
.
net
.
TetheringManager
.
TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION
;
import
static
android
.
net
.
TetheringManager
.
TETHER_ERROR_NO_ERROR
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
mockito
.
ArgumentMatchers
.
eq
;
import
static
org
.
mockito
.
Mockito
.
reset
;
import
static
org
.
mockito
.
Mockito
.
verify
;
import
static
org
.
mockito
.
Mockito
.
verifyNoMoreInteractions
;
import
static
org
.
mockito
.
Mockito
.
when
;
import
android.app.UiAutomation
;
import
android.content.Intent
;
import
android.net.IIntResultListener
;
import
android.net.ITetheringConnector
;
import
android.net.ITetheringEventCallback
;
import
android.net.TetheringRequestParcel
;
import
android.os.Bundle
;
import
android.os.Handler
;
import
android.os.ResultReceiver
;
import
androidx.test.InstrumentationRegistry
;
...
...
@@ -51,13 +60,14 @@ import org.mockito.MockitoAnnotations;
@SmallTest
public
final
class
TetheringServiceTest
{
private
static
final
String
TEST_IFACE_NAME
=
"test_wlan0"
;
private
static
final
String
TEST_CALLER_PKG
=
"
test_pkg
"
;
private
static
final
String
TEST_CALLER_PKG
=
"
com.android.shell
"
;
private
static
final
String
TEST_ATTRIBUTION_TAG
=
null
;
@Mock
private
ITetheringEventCallback
mITetheringEventCallback
;
@Rule
public
ServiceTestRule
mServiceTestRule
;
private
Tethering
mTethering
;
private
Intent
mMockServiceIntent
;
private
ITetheringConnector
mTetheringConnector
;
private
UiAutomation
mUiAutomation
;
private
class
TestTetheringResult
extends
IIntResultListener
.
Stub
{
private
int
mResult
=
-
1
;
// Default value that does not match any result code.
...
...
@@ -71,9 +81,26 @@ public final class TetheringServiceTest {
}
}
private
class
MyResultReceiver
extends
ResultReceiver
{
MyResultReceiver
(
Handler
handler
)
{
super
(
handler
);
}
private
int
mResult
=
-
1
;
// Default value that does not match any result code.
@Override
protected
void
onReceiveResult
(
int
resultCode
,
Bundle
resultData
)
{
mResult
=
resultCode
;
}
public
void
assertResult
(
int
expected
)
{
assertEquals
(
expected
,
mResult
);
}
}
@Before
public
void
setUp
()
throws
Exception
{
MockitoAnnotations
.
initMocks
(
this
);
mUiAutomation
=
InstrumentationRegistry
.
getInstrumentation
().
getUiAutomation
();
mServiceTestRule
=
new
ServiceTestRule
();
mMockServiceIntent
=
new
Intent
(
InstrumentationRegistry
.
getTargetContext
(),
...
...
@@ -83,115 +110,364 @@ public final class TetheringServiceTest {
mTetheringConnector
=
mockConnector
.
getTetheringConnector
();
final
MockTetheringService
service
=
mockConnector
.
getService
();
mTethering
=
service
.
getTethering
();
when
(
mTethering
.
isTetheringSupported
()).
thenReturn
(
true
);
}
@After
public
void
tearDown
()
throws
Exception
{
mServiceTestRule
.
unbindService
();
mUiAutomation
.
dropShellPermissionIdentity
();
}
@Test
public
void
testTether
()
throws
Exception
{
private
interface
TestTetheringCall
{
void
runTetheringCall
(
TestTetheringResult
result
)
throws
Exception
;
}
private
void
runAsNoPermission
(
final
TestTetheringCall
test
)
throws
Exception
{
runTetheringCall
(
test
,
new
String
[
0
]);
}
private
void
runAsTetherPrivileged
(
final
TestTetheringCall
test
)
throws
Exception
{
runTetheringCall
(
test
,
TETHER_PRIVILEGED
);
}
private
void
runAsAccessNetworkState
(
final
TestTetheringCall
test
)
throws
Exception
{
runTetheringCall
(
test
,
ACCESS_NETWORK_STATE
);
}
private
void
runAsWriteSettings
(
final
TestTetheringCall
test
)
throws
Exception
{
runTetheringCall
(
test
,
WRITE_SETTINGS
);
}
private
void
runTetheringCall
(
final
TestTetheringCall
test
,
String
...
permissions
)
throws
Exception
{
if
(
permissions
.
length
>
0
)
mUiAutomation
.
adoptShellPermissionIdentity
(
permissions
);
try
{
when
(
mTethering
.
isTetheringSupported
()).
thenReturn
(
true
);
test
.
runTetheringCall
(
new
TestTetheringResult
());
}
finally
{
mUiAutomation
.
dropShellPermissionIdentity
();
}
}
private
void
verifyNoMoreInteractionsForTethering
()
{
verifyNoMoreInteractions
(
mTethering
);
verifyNoMoreInteractions
(
mITetheringEventCallback
);
reset
(
mTethering
,
mITetheringEventCallback
);
}
private
void
runTether
(
final
TestTetheringResult
result
)
throws
Exception
{
when
(
mTethering
.
tether
(
TEST_IFACE_NAME
)).
thenReturn
(
TETHER_ERROR_NO_ERROR
);
final
TestTetheringResult
result
=
new
TestTetheringResult
();
mTetheringConnector
.
tether
(
TEST_IFACE_NAME
,
TEST_CALLER_PKG
,
TEST_ATTRIBUTION_TAG
,
result
);
verify
(
mTethering
).
isTetheringSupported
();
verify
(
mTethering
).
tether
(
TEST_IFACE_NAME
);
verifyNoMoreInteractions
(
mTethering
);
result
.
assertResult
(
TETHER_ERROR_NO_ERROR
);
}
@Test
public
void
testUntether
()
throws
Exception
{
public
void
testTether
()
throws
Exception
{
runAsNoPermission
((
result
)
->
{
mTetheringConnector
.
tether
(
TEST_IFACE_NAME
,
TEST_CALLER_PKG
,
TEST_ATTRIBUTION_TAG
,
result
);
verify
(
mTethering
).
isTetherProvisioningRequired
();
result
.
assertResult
(
TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION
);
verifyNoMoreInteractionsForTethering
();
});
runAsTetherPrivileged
((
result
)
->
{
runTether
(
result
);
verifyNoMoreInteractionsForTethering
();
});
runAsWriteSettings
((
result
)
->
{
runTether
(
result
);
verify
(
mTethering
).
isTetherProvisioningRequired
();
verifyNoMoreInteractionsForTethering
();
});
}
private
void
runUnTether
(
final
TestTetheringResult
result
)
throws
Exception
{
when
(
mTethering
.
untether
(
TEST_IFACE_NAME
)).
thenReturn
(
TETHER_ERROR_NO_ERROR
);
final
TestTetheringResult
result
=
new
TestTetheringResult
();
mTetheringConnector
.
untether
(
TEST_IFACE_NAME
,
TEST_CALLER_PKG
,
TEST_ATTRIBUTION_TAG
,
result
);
verify
(
mTethering
).
isTetheringSupported
();
verify
(
mTethering
).
untether
(
TEST_IFACE_NAME
);
verifyNoMoreInteractions
(
mTethering
);
result
.
assertResult
(
TETHER_ERROR_NO_ERROR
);
}
@Test
public
void
testSetUsbTethering
()
throws
Exception
{
public
void
testUntether
()
throws
Exception
{
runAsNoPermission
((
result
)
->
{
mTetheringConnector
.
untether
(
TEST_IFACE_NAME
,
TEST_CALLER_PKG
,
TEST_ATTRIBUTION_TAG
,
result
);
verify
(
mTethering
).
isTetherProvisioningRequired
();
result
.
assertResult
(
TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION
);
verifyNoMoreInteractionsForTethering
();
});
runAsTetherPrivileged
((
result
)
->
{
runUnTether
(
result
);
verifyNoMoreInteractionsForTethering
();
});
runAsWriteSettings
((
result
)
->
{
runUnTether
(
result
);
verify
(
mTethering
).
isTetherProvisioningRequired
();
verifyNoMoreInteractionsForTethering
();
});
}
private
void
runSetUsbTethering
(
final
TestTetheringResult
result
)
throws
Exception
{
when
(
mTethering
.
setUsbTethering
(
true
/* enable */
)).
thenReturn
(
TETHER_ERROR_NO_ERROR
);
final
TestTetheringResult
result
=
new
TestTetheringResult
();
mTetheringConnector
.
setUsbTethering
(
true
/* enable */
,
TEST_CALLER_PKG
,
TEST_ATTRIBUTION_TAG
,
result
);
verify
(
mTethering
).
isTetheringSupported
();
verify
(
mTethering
).
setUsbTethering
(
true
/* enable */
);
verifyNoMoreInteractions
(
mTethering
);
result
.
assertResult
(
TETHER_ERROR_NO_ERROR
);
}
@Test
public
void
testSetUsbTethering
()
throws
Exception
{
runAsNoPermission
((
result
)
->
{
mTetheringConnector
.
setUsbTethering
(
true
/* enable */
,
TEST_CALLER_PKG
,
TEST_ATTRIBUTION_TAG
,
result
);
verify
(
mTethering
).
isTetherProvisioningRequired
();
result
.
assertResult
(
TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION
);
verifyNoMoreInteractionsForTethering
();
});
runAsTetherPrivileged
((
result
)
->
{
runSetUsbTethering
(
result
);
verifyNoMoreInteractionsForTethering
();
});
runAsWriteSettings
((
result
)
->
{
runSetUsbTethering
(
result
);
verify
(
mTethering
).
isTetherProvisioningRequired
();
verifyNoMoreInteractionsForTethering
();
});
}
private
void
runStartTethering
(
final
TestTetheringResult
result
,
final
TetheringRequestParcel
request
)
throws
Exception
{
mTetheringConnector
.
startTethering
(
request
,
TEST_CALLER_PKG
,
TEST_ATTRIBUTION_TAG
,
result
);
verify
(
mTethering
).
isTetheringSupported
();
verify
(
mTethering
).
startTethering
(
eq
(
request
),
eq
(
result
));
}
@Test
public
void
testStartTethering
()
throws
Exception
{
final
TestTetheringResult
result
=
new
TestTetheringResult
();
final
TetheringRequestParcel
request
=
new
TetheringRequestParcel
();
request
.
tetheringType
=
TETHERING_WIFI
;
mTetheringConnector
.
startTethering
(
request
,
TEST_CALLER_PKG
,
TEST_ATTRIBUTION_TAG
,
result
);
verify
(
mTethering
).
isTetheringSupported
();
verify
(
mTethering
).
startTethering
(
eq
(
request
),
eq
(
result
));
verifyNoMoreInteractions
(
mTethering
);
runAsNoPermission
((
result
)
->
{
mTetheringConnector
.
startTethering
(
request
,
TEST_CALLER_PKG
,
TEST_ATTRIBUTION_TAG
,
result
);
verify
(
mTethering
).
isTetherProvisioningRequired
();
result
.
assertResult
(
TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION
);
verifyNoMoreInteractionsForTethering
();
});
runAsTetherPrivileged
((
result
)
->
{
runStartTethering
(
result
,
request
);
verifyNoMoreInteractionsForTethering
();
});
runAsWriteSettings
((
result
)
->
{
runStartTethering
(
result
,
request
);
verify
(
mTethering
).
isTetherProvisioningRequired
();
verifyNoMoreInteractionsForTethering
();
});
}
@Test
public
void
testStopTethering
()
throws
Exception
{
final
TestTetheringResult
result
=
new
TestTetheringResult
();
mTetheringConnector
.
stopTethering
(
TETHERING_WIFI
,
TEST_CALLER_PKG
,
TEST_ATTRIBUTION_TAG
,
result
);
public
void
testStartTetheringWithExemptFromEntitlementCheck
()
throws
Exception
{
final
TetheringRequestParcel
request
=
new
TetheringRequestParcel
();
request
.
tetheringType
=
TETHERING_WIFI
;
request
.
exemptFromEntitlementCheck
=
true
;
runAsTetherPrivileged
((
result
)
->
{
runStartTethering
(
result
,
request
);
verifyNoMoreInteractionsForTethering
();
});
runAsWriteSettings
((
result
)
->
{
mTetheringConnector
.
startTethering
(
request
,
TEST_CALLER_PKG
,
TEST_ATTRIBUTION_TAG
,
result
);
result
.
assertResult
(
TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION
);
verifyNoMoreInteractionsForTethering
();
});
}
private
void
runStopTethering
(
final
TestTetheringResult
result
)
throws
Exception
{
mTetheringConnector
.
stopTethering
(
TETHERING_WIFI
,
TEST_CALLER_PKG
,
TEST_ATTRIBUTION_TAG
,
result
);
verify
(
mTethering
).
isTetheringSupported
();
verify
(
mTethering
).
stopTethering
(
TETHERING_WIFI
);
verifyNoMoreInteractions
(
mTethering
);
result
.
assertResult
(
TETHER_ERROR_NO_ERROR
);
}
@Test
public
void
testRequestLatestTetheringEntitlementResult
()
throws
Exception
{
final
ResultReceiver
result
=
new
ResultReceiver
(
null
);
public
void
testStopTethering
()
throws
Exception
{
runAsNoPermission
((
result
)
->
{
mTetheringConnector
.
stopTethering
(
TETHERING_WIFI
,
TEST_CALLER_PKG
,
TEST_ATTRIBUTION_TAG
,
result
);
verify
(
mTethering
).
isTetherProvisioningRequired
();
result
.
assertResult
(
TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION
);
verifyNoMoreInteractionsForTethering
();
});
runAsTetherPrivileged
((
result
)
->
{
runStopTethering
(
result
);
verifyNoMoreInteractionsForTethering
();
});
runAsWriteSettings
((
result
)
->
{
runStopTethering
(
result
);
verify
(
mTethering
).
isTetherProvisioningRequired
();
verifyNoMoreInteractionsForTethering
();
});
}
private
void
runRequestLatestTetheringEntitlementResult
()
throws
Exception
{
final
MyResultReceiver
result
=
new
MyResultReceiver
(
null
);
mTetheringConnector
.
requestLatestTetheringEntitlementResult
(
TETHERING_WIFI
,
result
,
true
/* showEntitlementUi */
,
TEST_CALLER_PKG
,
TEST_ATTRIBUTION_TAG
);
verify
(
mTethering
).
isTetheringSupported
();
verify
(
mTethering
).
requestLatestTetheringEntitlementResult
(
eq
(
TETHERING_WIFI
),
eq
(
result
),
eq
(
true
)
/* showEntitlementUi */
);
verifyNoMoreInteractions
(
mTethering
);
}
@Test
public
void
testRegisterTetheringEventCallback
()
throws
Exception
{
public
void
testRequestLatestTetheringEntitlementResult
()
throws
Exception
{
// Run as no permission.
final
MyResultReceiver
result
=
new
MyResultReceiver
(
null
);
mTetheringConnector
.
requestLatestTetheringEntitlementResult
(
TETHERING_WIFI
,
result
,
true
/* showEntitlementUi */
,
TEST_CALLER_PKG
,
TEST_ATTRIBUTION_TAG
);
verify
(
mTethering
).
isTetherProvisioningRequired
();
result
.
assertResult
(
TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION
);
verifyNoMoreInteractions
(
mTethering
);
runAsTetherPrivileged
((
none
)
->
{
runRequestLatestTetheringEntitlementResult
();
verifyNoMoreInteractionsForTethering
();
});
runAsWriteSettings
((
none
)
->
{
runRequestLatestTetheringEntitlementResult
();
verify
(
mTethering
).
isTetherProvisioningRequired
();
verifyNoMoreInteractionsForTethering
();
});
}
private
void
runRegisterTetheringEventCallback
()
throws
Exception
{
mTetheringConnector
.
registerTetheringEventCallback
(
mITetheringEventCallback
,
TEST_CALLER_PKG
);
verify
(
mTethering
).
registerTetheringEventCallback
(
eq
(
mITetheringEventCallback
));
verifyNoMoreInteractions
(
mTethering
);
}
@Test
public
void
testUnregisterTetheringEventCallback
()
throws
Exception
{
public
void
testRegisterTetheringEventCallback
()
throws
Exception
{
runAsNoPermission
((
result
)
->
{
mTetheringConnector
.
registerTetheringEventCallback
(
mITetheringEventCallback
,
TEST_CALLER_PKG
);
verify
(
mITetheringEventCallback
).
onCallbackStopped
(
TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION
);
verifyNoMoreInteractionsForTethering
();
});
runAsTetherPrivileged
((
none
)
->
{
runRegisterTetheringEventCallback
();
verifyNoMoreInteractionsForTethering
();
});
runAsAccessNetworkState
((
none
)
->
{
runRegisterTetheringEventCallback
();
verifyNoMoreInteractionsForTethering
();
});
}
private
void
runUnregisterTetheringEventCallback
()
throws
Exception
{
mTetheringConnector
.
unregisterTetheringEventCallback
(
mITetheringEventCallback
,
TEST_CALLER_PKG
);
verify
(
mTethering
).
unregisterTetheringEventCallback
(
eq
(
mITetheringEventCallback
));
verifyNoMoreInteractions
(
mTethering
);
verify
(
mTethering
).
unregisterTetheringEventCallback
(
eq
(
mITetheringEventCallback
));
}
@Test
public
void
testStopAllTethering
()
throws
Exception
{
final
TestTetheringResult
result
=
new
TestTetheringResult
();
public
void
testUnregisterTetheringEventCallback
()
throws
Exception
{
runAsNoPermission
((
result
)
->
{
mTetheringConnector
.
unregisterTetheringEventCallback
(
mITetheringEventCallback
,
TEST_CALLER_PKG
);
verify
(
mITetheringEventCallback
).
onCallbackStopped
(
TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION
);
verifyNoMoreInteractionsForTethering
();
});
runAsTetherPrivileged
((
none
)
->
{
runUnregisterTetheringEventCallback
();
verifyNoMoreInteractionsForTethering
();
});
runAsAccessNetworkState
((
none
)
->
{
runUnregisterTetheringEventCallback
();
verifyNoMoreInteractionsForTethering
();
});
}
private
void
runStopAllTethering
(
final
TestTetheringResult
result
)
throws
Exception
{
mTetheringConnector
.
stopAllTethering
(
TEST_CALLER_PKG
,
TEST_ATTRIBUTION_TAG
,
result
);
verify
(
mTethering
).
isTetheringSupported
();
verify
(
mTethering
).
untetherAll
();
verifyNoMoreInteractions
(
mTethering
);
result
.
assertResult
(
TETHER_ERROR_NO_ERROR
);
}
@Test
public
void
testIsTetheringSupported
()
throws
Exception
{
final
TestTetheringResult
result
=
new
TestTetheringResult
();
public
void
testStopAllTethering
()
throws
Exception
{
runAsNoPermission
((
result
)
->
{
mTetheringConnector
.
stopAllTethering
(
TEST_CALLER_PKG
,
TEST_ATTRIBUTION_TAG
,
result
);
verify
(
mTethering
).
isTetherProvisioningRequired
();
result
.
assertResult
(
TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION
);
verifyNoMoreInteractionsForTethering
();
});
runAsTetherPrivileged
((
result
)
->
{
runStopAllTethering
(
result
);
verifyNoMoreInteractionsForTethering
();
});
runAsWriteSettings
((
result
)
->
{
runStopAllTethering
(
result
);
verify
(
mTethering
).
isTetherProvisioningRequired
();
verifyNoMoreInteractionsForTethering
();
});
}
private
void
runIsTetheringSupported
(
final
TestTetheringResult
result
)
throws
Exception
{
mTetheringConnector
.
isTetheringSupported
(
TEST_CALLER_PKG
,
TEST_ATTRIBUTION_TAG
,
result
);
verify
(
mTethering
).
isTetheringSupported
();
verifyNoMoreInteractions
(
mTethering
);
result
.
assertResult
(
TETHER_ERROR_NO_ERROR
);
}
@Test
public
void
testIsTetheringSupported
()
throws
Exception
{
runAsNoPermission
((
result
)
->
{
mTetheringConnector
.
isTetheringSupported
(
TEST_CALLER_PKG
,
TEST_ATTRIBUTION_TAG
,
result
);
verify
(
mTethering
).
isTetherProvisioningRequired
();
result
.
assertResult
(
TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION
);
verifyNoMoreInteractionsForTethering
();
});
runAsTetherPrivileged
((
result
)
->
{
runIsTetheringSupported
(
result
);
verifyNoMoreInteractionsForTethering
();
});
runAsWriteSettings
((
result
)
->
{
runIsTetheringSupported
(
result
);
verify
(
mTethering
).
isTetherProvisioningRequired
();
verifyNoMoreInteractionsForTethering
();
});
}
}
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment